cloudshell-cli 4.0.5__zip → 4.1.1__zip

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. {cloudshell-cli-4.0.5/cloudshell_cli.egg-info → cloudshell-cli-4.1.1}/PKG-INFO +2 -5
  2. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/configurator.py +37 -38
  3. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/examples/cli_demo/connect_to_switch/SwitchClihandler.py +0 -1
  4. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/examples/cli_demo/connect_to_switch/runner.py +2 -2
  5. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/examples/cli_demo/connect_to_ubuntu/runnur.py +1 -2
  6. cloudshell-cli-4.1.1/cloudshell/cli/factory/session_factory.py +96 -0
  7. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/cli_service_impl.py +2 -1
  8. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/command_mode_helper.py +2 -2
  9. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/expect_session.py +46 -1
  10. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/scpi_session.py +0 -1
  11. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/ssh_session.py +24 -25
  12. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/tcp_session.py +5 -16
  13. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/telnet_session.py +4 -19
  14. cloudshell-cli-4.1.1/cloudshell/cli/session_pool_context_manager.py +0 -0
  15. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1/cloudshell_cli.egg-info}/PKG-INFO +2 -5
  16. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell_cli.egg-info/SOURCES.txt +4 -0
  17. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell_cli.egg-info/requires.txt +1 -0
  18. cloudshell-cli-4.1.1/dev_requirements.txt +4 -0
  19. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/requirements.txt +1 -0
  20. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/setup.py +1 -0
  21. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/session/test_expect_session.py +8 -5
  22. cloudshell-cli-4.1.1/tests/cli/session/test_expect_session_py3.py +13 -0
  23. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/session/test_ssh_session.py +11 -16
  24. cloudshell-cli-4.1.1/tests/conftest.py +15 -0
  25. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tox.ini +1 -1
  26. cloudshell-cli-4.1.1/version.txt +1 -0
  27. cloudshell-cli-4.0.5/dev_requirements.txt +0 -5
  28. cloudshell-cli-4.0.5/version.txt +0 -1
  29. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/LICENSE +0 -0
  30. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/MANIFEST.in +0 -0
  31. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/README.txt +0 -0
  32. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/__init__.py +0 -0
  33. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/__init__.py +0 -0
  34. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/cli.py +0 -0
  35. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/command_template/__init__.py +0 -0
  36. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/command_template/command_template.py +0 -0
  37. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/command_template/command_template_executor.py +0 -0
  38. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/examples/__init__.py +0 -0
  39. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/examples/cli_demo/__init__.py +0 -0
  40. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/examples/cli_demo/connect_to_AWS/__init__.py +0 -0
  41. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/examples/cli_demo/connect_to_AWS/runnur.py +0 -0
  42. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/examples/cli_demo/connect_to_switch/__init__.py +0 -0
  43. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/examples/cli_demo/connect_to_ubuntu/__init__.py +0 -0
  44. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/examples/cli_demo/connect_to_ubuntu/readfrom.txt +0 -0
  45. {cloudshell-cli-4.0.5/cloudshell/cli/service → cloudshell-cli-4.1.1/cloudshell/cli/factory}/__init__.py +0 -0
  46. /cloudshell-cli-4.0.5/cloudshell/cli/session_pool_context_manager.py → /cloudshell-cli-4.1.1/cloudshell/cli/service/__init__.py +0 -0
  47. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/cli.py +0 -0
  48. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/cli_exception.py +0 -0
  49. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/cli_service.py +0 -0
  50. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/command_mode.py +0 -0
  51. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/node.py +0 -0
  52. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/session_manager.py +0 -0
  53. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/session_manager_impl.py +0 -0
  54. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/session_pool.py +0 -0
  55. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/session_pool_context_manager.py +0 -0
  56. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/service/session_pool_manager.py +0 -0
  57. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/__init__.py +0 -0
  58. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/connection_params.py +0 -0
  59. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/helper/__init__.py +0 -0
  60. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/helper/normalize_buffer.py +0 -0
  61. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/session.py +0 -0
  62. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/session_exceptions.py +0 -0
  63. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell/cli/session/tl1_session.py +0 -0
  64. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell_cli.egg-info/dependency_links.txt +0 -0
  65. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/cloudshell_cli.egg-info/top_level.txt +0 -0
  66. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/setup.cfg +0 -0
  67. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/test_requirements.txt +0 -0
  68. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/__init__.py +0 -0
  69. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/__init__.py +0 -0
  70. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/command_template/__init__.py +0 -0
  71. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/command_template/test_command_template.py +0 -0
  72. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/session/__init__.py +0 -0
  73. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/session/test_connection_params.py +0 -0
  74. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/session/test_telnet_session.py +0 -0
  75. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/test_cli.py +0 -0
  76. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/test_cli_service_impl.py +0 -0
  77. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/test_command_mode.py +0 -0
  78. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/test_command_mode_helper.py +0 -0
  79. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/test_node.py +0 -0
  80. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/test_session_manager.py +0 -0
  81. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/test_session_pool_context_manager.py +0 -0
  82. {cloudshell-cli-4.0.5 → cloudshell-cli-4.1.1}/tests/cli/test_session_pool_manager.py +0 -0
@@ -1,14 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudshell-cli
3
- Version: 4.0.5
3
+ Version: 4.1.1
4
4
  Summary: QualiSystems CloudShell CLI support package
5
5
  Home-page: http://www.qualisystems.com/
6
6
  Author: QualiSystems
7
7
  Author-email: info@qualisystems.com
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
8
  Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, <4
11
9
  License-File: LICENSE
12
10
 
13
- UNKNOWN
14
-
11
+ QualiSystems CloudShell CLI support package
@@ -2,7 +2,13 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  import sys
4
4
  from abc import ABCMeta, abstractmethod
5
+ from collections import defaultdict
5
6
 
7
+ from cloudshell.cli.factory.session_factory import (
8
+ CloudInfoAccessKeySessionFactory,
9
+ GenericSessionFactory,
10
+ SessionFactory,
11
+ )
6
12
  from cloudshell.cli.service.cli import CLI
7
13
  from cloudshell.cli.session.ssh_session import SSHSession
8
14
  from cloudshell.cli.session.telnet_session import TelnetSession
@@ -16,39 +22,33 @@ else:
16
22
 
17
23
 
18
24
  class CLIServiceConfigurator(object):
19
- REGISTERED_SESSIONS = (SSHSession, TelnetSession)
20
-
21
- def __init__(self, resource_config, logger, cli=None, registered_sessions=None):
25
+ REGISTERED_SESSIONS = (CloudInfoAccessKeySessionFactory(SSHSession), TelnetSession)
26
+ """Using factories instead of """
27
+
28
+ def __init__(
29
+ self,
30
+ resource_config,
31
+ logger,
32
+ cli=None,
33
+ registered_sessions=None,
34
+ access_key=None,
35
+ access_key_passphrase=None,
36
+ ):
22
37
  """Initialize CLI service configurator.
23
38
 
24
39
  :param cloudshell.shell.standards.resource_config_generic_models.GenericCLIConfig resource_config: # noqa: E501
25
40
  :param logging.Logger logger:
26
41
  :param cloudshell.cli.service.cli.CLI cli:
27
42
  :param registered_sessions: Session types and order
43
+ :param access_key: access key for the resource
44
+ :param access_key_passphrase: access key passphrase for the resource
28
45
  """
29
46
  self._cli = cli or CLI()
30
47
  self._resource_config = resource_config
31
48
  self._logger = logger
32
49
  self._registered_sessions = registered_sessions or self.REGISTERED_SESSIONS
33
-
34
- @property
35
- def _username(self):
36
- return self._resource_config.user
37
-
38
- @property
39
- @lru_cache()
40
- def _password(self):
41
- return self._resource_config.password
42
-
43
- @property
44
- def _resource_address(self):
45
- """Resource IP."""
46
- return self._resource_config.address
47
-
48
- @property
49
- def _port(self):
50
- """Connection port property, to open socket on."""
51
- return self._resource_config.cli_tcp_port
50
+ self._access_key = access_key
51
+ self._access_key_passphrase = access_key_passphrase
52
52
 
53
53
  @property
54
54
  def _cli_type(self):
@@ -58,29 +58,28 @@ class CLIServiceConfigurator(object):
58
58
  @property
59
59
  @lru_cache()
60
60
  def _session_dict(self):
61
- return {sess.SESSION_TYPE.lower(): [sess] for sess in self._registered_sessions}
61
+ session_dict = defaultdict(list)
62
+ for sess in self._registered_sessions:
63
+ session_dict[sess.SESSION_TYPE.lower()].append(sess)
64
+ return session_dict
62
65
 
63
66
  def _on_session_start(self, session, logger):
64
- """Perform some default commands when session just opened.
65
-
66
- Like 'no logging console'
67
- """
68
67
  pass
69
68
 
70
- @property
71
- @lru_cache()
72
- def _session_kwargs(self):
73
- return {
74
- "host": self._resource_address,
75
- "username": self._username,
76
- "password": self._password,
77
- "port": self._port,
78
- "on_session_start": self._on_session_start,
79
- }
69
+ def initialize_session(self, session):
70
+ if not isinstance(session, SessionFactory):
71
+ session = GenericSessionFactory(session)
72
+ return session.init_session(
73
+ self._resource_config,
74
+ self._logger,
75
+ on_session_start=self._on_session_start,
76
+ access_key=self._access_key,
77
+ access_key_passphrase=self._access_key_passphrase,
78
+ )
80
79
 
81
80
  def _defined_sessions(self):
82
81
  return [
83
- sess(**self._session_kwargs)
82
+ self.initialize_session(sess)
84
83
  for sess in self._session_dict.get(
85
84
  self._cli_type.lower(), self._registered_sessions
86
85
  )
@@ -96,7 +96,6 @@ class SwitchCliHandler(CliHandlerImpl):
96
96
  session.hardware_expect("exit", EnableCommandMode.PROMPT, logger)
97
97
 
98
98
  def enter_enable_mode(self, session, logger):
99
-
100
99
  enable_password = get_attribute_by_name(
101
100
  attribute_name="Enable Password", context=self._context
102
101
  )
@@ -40,6 +40,6 @@ if __name__ == "__main__":
40
40
  out = session.send_command("echo checking switch")
41
41
  with session.enter_mode(cli_handler.config_mode) as config_session:
42
42
  out = config_session.send_command("echo checking switch")
43
- print(out) # noqa: T001
43
+ print(out) # noqa: T201
44
44
  out = config_session.send_command("echo checking switch")
45
- print(out) # noqa: T001
45
+ print(out) # noqa: T201
@@ -13,7 +13,6 @@ class CliCommandMode(CommandMode):
13
13
  EXIT_COMMAND = "exit"
14
14
 
15
15
  def __init__(self, context):
16
-
17
16
  CommandMode.__init__(
18
17
  self,
19
18
  CliCommandMode.PROMPT,
@@ -45,6 +44,6 @@ if __name__ == "__main__":
45
44
 
46
45
  with cli.get_session(session, default_mode) as default_session:
47
46
  out = default_session.send_command("echo Cli Demo connected to Ubuntu Machine")
48
- print(out) # noqa: T001
47
+ print(out) # noqa: T201
49
48
  fd = open("<src_file_path>")
50
49
  default_session.session.upload_scp(fd, "<dst_file_path>", "<file_size>", "0666")
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ from abc import ABCMeta, abstractmethod
4
+
5
+ ABC = ABCMeta("ABC", (object,), {"__slots__": ()})
6
+
7
+
8
+ class SessionFactory(ABC):
9
+ """Session factory.
10
+
11
+ Help to initialize session for specified session class.
12
+ """
13
+
14
+ def __init__(self, session_class):
15
+ """:param session_class: Session class."""
16
+ self.session_class = session_class
17
+
18
+ @abstractmethod
19
+ def init_session(
20
+ self,
21
+ resource_config,
22
+ logger,
23
+ on_session_start=None,
24
+ access_key=None,
25
+ access_key_passphrase=None,
26
+ ):
27
+ """Initialize session instance.
28
+
29
+ Encapsulate the logic of the session instance creation.
30
+ :param resource_config:
31
+ :param logging.Logger logger:
32
+ :param on_session_start: function that will be called on session start
33
+ :param access_key: access key for the resource
34
+ :param access_key_passphrase: access key passphrase for the resource
35
+ """
36
+ raise NotImplementedError
37
+
38
+
39
+ class GenericSessionFactory(SessionFactory):
40
+ def init_session(
41
+ self,
42
+ resource_config,
43
+ logger,
44
+ on_session_start=None,
45
+ access_key=None,
46
+ access_key_passphrase=None,
47
+ ):
48
+ return self.session_class(
49
+ **self._session_kwargs(
50
+ resource_config,
51
+ logger,
52
+ on_session_start,
53
+ access_key,
54
+ access_key_passphrase,
55
+ )
56
+ )
57
+
58
+ @property
59
+ def SESSION_TYPE(self):
60
+ return self.session_class.SESSION_TYPE
61
+
62
+ def _session_kwargs(
63
+ self,
64
+ resource_config,
65
+ logger,
66
+ on_session_start,
67
+ access_key,
68
+ access_key_passphrase,
69
+ ):
70
+ return {
71
+ "host": resource_config.address,
72
+ "username": resource_config.user,
73
+ "password": resource_config.password,
74
+ "port": resource_config.cli_tcp_port,
75
+ "on_session_start": on_session_start,
76
+ }
77
+
78
+
79
+ class CloudInfoAccessKeySessionFactory(GenericSessionFactory):
80
+ def _session_kwargs(
81
+ self,
82
+ resource_config,
83
+ logger,
84
+ on_session_start,
85
+ access_key,
86
+ access_key_passphrase,
87
+ ):
88
+ return {
89
+ "host": resource_config.address,
90
+ "username": resource_config.user,
91
+ "password": resource_config.password,
92
+ "port": resource_config.cli_tcp_port,
93
+ "pkey": access_key,
94
+ "pkey_passphrase": access_key_passphrase,
95
+ "on_session_start": on_session_start,
96
+ }
@@ -158,7 +158,8 @@ class CliServiceImpl(CliService):
158
158
  steps = CommandModeHelper.calculate_route_steps(
159
159
  self.command_mode, requested_command_mode
160
160
  )
161
- list(map(lambda x: x(self, self._logger), steps))
161
+ for s in steps:
162
+ s(self, self._logger)
162
163
 
163
164
  def reconnect(self, timeout=None):
164
165
  """Reconnect session, keep current command mode.
@@ -35,7 +35,7 @@ class CommandModeHelper(NodeOperations):
35
35
 
36
36
  :rtype: OrderedDict
37
37
  """
38
- # noqa
38
+
39
39
  def _get_child_nodes(command_node):
40
40
  return reduce(
41
41
  lambda x, y: x + _get_child_nodes(y),
@@ -60,7 +60,7 @@ class CommandModeHelper(NodeOperations):
60
60
 
61
61
  :rtype: dict
62
62
  """
63
- # noqa
63
+
64
64
  def _create_child_modes(instance, child_dict):
65
65
  instance_dict = {}
66
66
  for mode_type in child_dict:
@@ -1,4 +1,5 @@
1
1
  import re
2
+ import socket
2
3
  import time
3
4
  from abc import ABCMeta, abstractmethod
4
5
  from collections import OrderedDict
@@ -158,6 +159,51 @@ class ExpectSession(Session, ABC):
158
159
  self.__class__.__name__, "Socket closed by timeout"
159
160
  )
160
161
 
162
+ def _receive(self, timeout, logger):
163
+ """Read session's buffer.
164
+
165
+ :type timeout: float
166
+ :type logger: logging.Logger
167
+ :rtype: str
168
+ """
169
+ timeout = timeout or self._timeout
170
+ self._set_timeout(timeout)
171
+ try:
172
+ data = self._read_str_data()
173
+ except socket.timeout:
174
+ raise SessionReadTimeout
175
+ if not data:
176
+ raise SessionReadEmptyData
177
+ return data
178
+
179
+ @abstractmethod
180
+ def _set_timeout(self, timeout):
181
+ pass
182
+
183
+ def _read_str_data(self):
184
+ byte_data = b""
185
+ for _ in range(5):
186
+ new_data = self._read_byte_data()
187
+
188
+ if not new_data:
189
+ str_data = byte_data.decode()
190
+ break
191
+
192
+ byte_data += new_data
193
+ try:
194
+ str_data = byte_data.decode()
195
+ except UnicodeDecodeError:
196
+ continue # byte data can end with a wrong utf8 symbol, read more
197
+ else:
198
+ break
199
+ else:
200
+ str_data = byte_data.decode()
201
+ return str_data
202
+
203
+ @abstractmethod
204
+ def _read_byte_data(self):
205
+ pass
206
+
161
207
  def _generate_command_pattern(self, command):
162
208
  """Generate command_pattern.
163
209
 
@@ -262,7 +308,6 @@ class ExpectSession(Session, ABC):
262
308
  self._loop_detector_max_combination_length,
263
309
  )
264
310
  while retries == 0 or retries_count < retries:
265
-
266
311
  read_buffer = self._receive_all(timeout, logger)
267
312
 
268
313
  if read_buffer:
@@ -38,7 +38,6 @@ class SCPISession(TCPSession):
38
38
  remove_command_from_output=True,
39
39
  **optional_args
40
40
  ):
41
-
42
41
  if ";:system:error?" not in command.lower():
43
42
  command += ";:system:error?"
44
43
 
@@ -1,15 +1,11 @@
1
- import socket
1
+ from io import StringIO
2
2
 
3
3
  import paramiko
4
4
  from scp import SCPClient
5
5
 
6
6
  from cloudshell.cli.session.connection_params import ConnectionParams
7
7
  from cloudshell.cli.session.expect_session import ExpectSession
8
- from cloudshell.cli.session.session_exceptions import (
9
- SessionException,
10
- SessionReadEmptyData,
11
- SessionReadTimeout,
12
- )
8
+ from cloudshell.cli.session.session_exceptions import SessionException
13
9
 
14
10
 
15
11
  class SSHSessionException(SessionException):
@@ -28,6 +24,7 @@ class SSHSession(ExpectSession, ConnectionParams):
28
24
  port=None,
29
25
  on_session_start=None,
30
26
  pkey=None,
27
+ pkey_passphrase=None,
31
28
  *args,
32
29
  **kwargs
33
30
  ):
@@ -42,6 +39,7 @@ class SSHSession(ExpectSession, ConnectionParams):
42
39
  self.username = username
43
40
  self.password = password
44
41
  self.pkey = pkey
42
+ self.pkey_passphrase = pkey_passphrase
45
43
 
46
44
  self._handler = None
47
45
  self._current_channel = None
@@ -58,6 +56,7 @@ class SSHSession(ExpectSession, ConnectionParams):
58
56
  self.username == other.username,
59
57
  self.password == other.password,
60
58
  self.pkey == other.pkey,
59
+ self.pkey_passphrase == other.pkey_passphrase,
61
60
  ]
62
61
  )
63
62
 
@@ -86,7 +85,7 @@ class SSHSession(ExpectSession, ConnectionParams):
86
85
  banner_timeout=30,
87
86
  allow_agent=False,
88
87
  look_for_keys=False,
89
- pkey=self.pkey,
88
+ pkey=self._get_pkey_object(self.pkey, self.pkey_passphrase, logger),
90
89
  )
91
90
  except Exception as e:
92
91
  logger.exception("Failed to initialize session:")
@@ -122,26 +121,11 @@ class SSHSession(ExpectSession, ConnectionParams):
122
121
  """
123
122
  self._current_channel.send(command)
124
123
 
125
- def _receive(self, timeout, logger):
126
- """Read session buffer.
127
-
128
- :param int timeout: time between retries
129
- :param logging.Logger logger:
130
- """
131
- # Set the channel timeout
132
- timeout = timeout if timeout else self._timeout
124
+ def _set_timeout(self, timeout):
133
125
  self._current_channel.settimeout(timeout)
134
126
 
135
- try:
136
- byte_data = self._current_channel.recv(self._buffer_size)
137
- data = byte_data.decode()
138
- except socket.timeout:
139
- raise SessionReadTimeout()
140
-
141
- if not data:
142
- raise SessionReadEmptyData()
143
-
144
- return data
127
+ def _read_byte_data(self):
128
+ return self._current_channel.recv(self._buffer_size)
145
129
 
146
130
  def upload_scp(
147
131
  self, file_stream, dest_pathname, file_size=None, dest_permissions="0601"
@@ -182,3 +166,18 @@ class SSHSession(ExpectSession, ConnectionParams):
182
166
  sftp.putfo(file_stream, dest_pathname)
183
167
  sftp.chmod(dest_pathname, int(dest_permissions, base=8))
184
168
  sftp.close()
169
+
170
+ @staticmethod
171
+ def _get_pkey_object(key_material, passphrase, logger):
172
+ """Try to detect private key type and return paramiko.PKey object."""
173
+ if not key_material:
174
+ return None
175
+
176
+ for cls in [paramiko.RSAKey, paramiko.DSSKey, paramiko.ECDSAKey]:
177
+ try:
178
+ key = cls.from_private_key(StringIO(key_material), password=passphrase)
179
+ except paramiko.ssh_exception.SSHException as e:
180
+ # Invalid key, try other key type
181
+ logger.warning(e)
182
+ else:
183
+ return key
@@ -2,14 +2,9 @@ import socket
2
2
 
3
3
  from cloudshell.cli.session.connection_params import ConnectionParams
4
4
  from cloudshell.cli.session.expect_session import ExpectSession
5
- from cloudshell.cli.session.session_exceptions import (
6
- SessionReadEmptyData,
7
- SessionReadTimeout,
8
- )
9
5
 
10
6
 
11
7
  class TCPSession(ExpectSession, ConnectionParams):
12
-
13
8
  SESSION_TYPE = "TCP"
14
9
  BUFFER_SIZE = 1024
15
10
 
@@ -49,17 +44,11 @@ class TCPSession(ExpectSession, ConnectionParams):
49
44
  """
50
45
  self._handler.sendall(command)
51
46
 
52
- def _receive(self, timeout, logger):
53
- """Read session buffer."""
54
- timeout = timeout if timeout else self._timeout
47
+ def _set_timeout(self, timeout):
55
48
  self._handler.settimeout(timeout)
56
49
 
57
- try:
58
- data = self._handler.recv(self._buffer_size)
59
- except socket.timeout:
60
- raise SessionReadTimeout()
61
-
62
- if not data:
63
- raise SessionReadEmptyData()
50
+ def _read_byte_data(self):
51
+ pass
64
52
 
65
- return data
53
+ def _read_str_data(self):
54
+ return self._handler.recv(self._buffer_size)
@@ -1,14 +1,9 @@
1
- import socket
2
1
  import telnetlib
3
2
  from collections import OrderedDict
4
3
 
5
4
  from cloudshell.cli.session.connection_params import ConnectionParams
6
5
  from cloudshell.cli.session.expect_session import ExpectSession
7
- from cloudshell.cli.session.session_exceptions import (
8
- SessionException,
9
- SessionReadEmptyData,
10
- SessionReadTimeout,
11
- )
6
+ from cloudshell.cli.session.session_exceptions import SessionException
12
7
 
13
8
 
14
9
  class TelnetSessionException(SessionException):
@@ -100,18 +95,8 @@ class TelnetSession(ExpectSession, ConnectionParams):
100
95
  byte_command = command.encode()
101
96
  self._handler.write(byte_command)
102
97
 
103
- def _receive(self, timeout, logger):
104
- """Read session buffer."""
105
- timeout = timeout if timeout else self._timeout
98
+ def _set_timeout(self, timeout):
106
99
  self._handler.get_socket().settimeout(timeout)
107
100
 
108
- try:
109
- byte_data = self._handler.read_some()
110
- except socket.timeout:
111
- raise SessionReadTimeout()
112
-
113
- if not byte_data:
114
- raise SessionReadEmptyData()
115
-
116
- data = byte_data.decode()
117
- return data
101
+ def _read_byte_data(self):
102
+ self._handler.read_some()
@@ -1,14 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudshell-cli
3
- Version: 4.0.5
3
+ Version: 4.1.1
4
4
  Summary: QualiSystems CloudShell CLI support package
5
5
  Home-page: http://www.qualisystems.com/
6
6
  Author: QualiSystems
7
7
  Author-email: info@qualisystems.com
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
8
  Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, <4
11
9
  License-File: LICENSE
12
10
 
13
- UNKNOWN
14
-
11
+ QualiSystems CloudShell CLI support package
@@ -26,6 +26,8 @@ cloudshell/cli/examples/cli_demo/connect_to_switch/runner.py
26
26
  cloudshell/cli/examples/cli_demo/connect_to_ubuntu/__init__.py
27
27
  cloudshell/cli/examples/cli_demo/connect_to_ubuntu/readfrom.txt
28
28
  cloudshell/cli/examples/cli_demo/connect_to_ubuntu/runnur.py
29
+ cloudshell/cli/factory/__init__.py
30
+ cloudshell/cli/factory/session_factory.py
29
31
  cloudshell/cli/service/__init__.py
30
32
  cloudshell/cli/service/cli.py
31
33
  cloudshell/cli/service/cli_exception.py
@@ -57,6 +59,7 @@ cloudshell_cli.egg-info/dependency_links.txt
57
59
  cloudshell_cli.egg-info/requires.txt
58
60
  cloudshell_cli.egg-info/top_level.txt
59
61
  tests/__init__.py
62
+ tests/conftest.py
60
63
  tests/cli/__init__.py
61
64
  tests/cli/test_cli.py
62
65
  tests/cli/test_cli_service_impl.py
@@ -71,5 +74,6 @@ tests/cli/command_template/test_command_template.py
71
74
  tests/cli/session/__init__.py
72
75
  tests/cli/session/test_connection_params.py
73
76
  tests/cli/session/test_expect_session.py
77
+ tests/cli/session/test_expect_session_py3.py
74
78
  tests/cli/session/test_ssh_session.py
75
79
  tests/cli/session/test_telnet_session.py
@@ -13,3 +13,4 @@ PyNaCl<1.5,>=1.0.1
13
13
  bcrypt~=3.2.0
14
14
  cryptography<37,>=3.3
15
15
  PyNaCl>=1.5
16
+ six
@@ -0,0 +1,4 @@
1
+ pre-commit
2
+ tox
3
+ -r test_requirements.txt
4
+ -r requirements.txt
@@ -7,3 +7,4 @@ PyNaCl>=1.5 ; python_version > "3.7"
7
7
  paramiko>=2.6,<2.7
8
8
  scp>=0.13,<1
9
9
  functools32; python_version <= '2.7'
10
+ six; python_version > "3.7"
@@ -26,5 +26,6 @@ setup(
26
26
  version=version_from_file,
27
27
  package_data={"": ["*.txt"]},
28
28
  description="QualiSystems CloudShell CLI support package",
29
+ long_description="QualiSystems CloudShell CLI support package",
29
30
  include_package_data=True,
30
31
  )
@@ -15,7 +15,7 @@ except ImportError:
15
15
  from mock import MagicMock, Mock, call, patch
16
16
 
17
17
 
18
- class TestExpectSessionException(Exception):
18
+ class ExpectSessionException(Exception):
19
19
  pass
20
20
 
21
21
 
@@ -32,10 +32,13 @@ class ExpectSessionImpl(ExpectSession):
32
32
  def disconnect(self):
33
33
  pass
34
34
 
35
- def _receive(self, timeout, logger):
35
+ def _send(self, command, logger):
36
36
  pass
37
37
 
38
- def _send(self, command, logger):
38
+ def _read_byte_data(self):
39
+ pass
40
+
41
+ def _set_timeout(self, timeout):
39
42
  pass
40
43
 
41
44
 
@@ -98,7 +101,7 @@ class TestExpectSession(TestCase):
98
101
  self._instance._clear_buffer(timeout, self._logger)
99
102
 
100
103
  def test_clear_buffer_exit_with_no_data(self, normalize_buffer, loops_detected):
101
- self._receive.side_effect = ["", TestExpectSessionException("Breaking loop")]
104
+ self._receive.side_effect = ["", ExpectSessionException("Breaking loop")]
102
105
  timeout = Mock()
103
106
  self._instance._clear_buffer(timeout, self._logger)
104
107
  self.assertTrue(True)
@@ -109,7 +112,7 @@ class TestExpectSession(TestCase):
109
112
  self._receive.side_effect = [
110
113
  "test",
111
114
  "",
112
- TestExpectSessionException("Breaking loop"),
115
+ ExpectSessionException("Breaking loop"),
113
116
  ]
114
117
  timeout = Mock()
115
118
  self._instance._clear_buffer(timeout, self._logger)
@@ -0,0 +1,13 @@
1
+ from tests.cli.session.test_expect_session import ExpectSessionImpl
2
+
3
+
4
+ def test_expect_session_read_wrong_utf8_symbol(logger):
5
+ class TestSession(ExpectSessionImpl):
6
+ data = [b"\xe2\x80\x99hi\xe2", b"\x80\x99"] # ’hi’
7
+
8
+ def _read_byte_data(self):
9
+ return self.data.pop(0)
10
+
11
+ session = TestSession()
12
+
13
+ assert session._receive(1, logger) == "’hi’"
@@ -46,7 +46,7 @@ y9/ziFJPPMN703N1dJ0e1oRWpfSFUzlGn1OgF1EY9qQTwQK4PL9bhyJi1/MswZHB
46
46
  /XmDlKr7An89Iy+B/Q9ncja2FfK49AP23AMYKhdWBrEq+enXwjkr5Uz8ffD3eagD
47
47
  VZxBlCGUp4bmlS5s7sttMZRvJq0CXfxy+q8Qe0sz/CyOAN/J8iZu8AJyp04DQk4d
48
48
  -----END RSA PRIVATE KEY-----
49
- """
49
+ """ # noqa
50
50
  KEY_PASSPHRASE = "quali1234"
51
51
 
52
52
 
@@ -443,9 +443,6 @@ class TestSshSession(TestCase):
443
443
  )
444
444
  )
445
445
 
446
- pkey = paramiko.RSAKey.from_private_key(
447
- StringIO(KEY_WITH_PASSPHRASE), password=KEY_PASSPHRASE
448
- )
449
446
  self.assertFalse(
450
447
  self._instance.__eq__(
451
448
  SSHSession(
@@ -454,23 +451,22 @@ class TestSshSession(TestCase):
454
451
  "",
455
452
  port=self._port,
456
453
  on_session_start=self._on_session_start,
457
- pkey=pkey,
454
+ pkey=KEY_WITH_PASSPHRASE,
455
+ pkey_passphrase=KEY_PASSPHRASE,
458
456
  )
459
457
  )
460
458
  )
461
459
 
462
460
  @patch("cloudshell.cli.session.ssh_session.ExpectSession")
463
461
  def test_eq_rsa(self, expect_session):
464
- pkey = paramiko.RSAKey.from_private_key(
465
- StringIO(KEY_WITH_PASSPHRASE), password=KEY_PASSPHRASE
466
- )
467
462
  self._instance = SSHSession(
468
463
  self._hostname,
469
464
  self._username,
470
465
  self._password,
471
466
  port=self._port,
472
467
  on_session_start=self._on_session_start,
473
- pkey=pkey,
468
+ pkey=KEY_WITH_PASSPHRASE,
469
+ pkey_passphrase=KEY_PASSPHRASE,
474
470
  )
475
471
 
476
472
  self.assertTrue(
@@ -481,7 +477,8 @@ class TestSshSession(TestCase):
481
477
  self._password,
482
478
  port=self._port,
483
479
  on_session_start=self._on_session_start,
484
- pkey=pkey,
480
+ pkey=KEY_WITH_PASSPHRASE,
481
+ pkey_passphrase=KEY_PASSPHRASE,
485
482
  )
486
483
  )
487
484
  )
@@ -640,16 +637,13 @@ class TestSshSession(TestCase):
640
637
  "",
641
638
  port=server.port,
642
639
  on_session_start=self._on_session_start,
643
- pkey=pkey,
640
+ pkey=KEY_WITH_PASSPHRASE,
641
+ pkey_passphrase=KEY_PASSPHRASE,
644
642
  )
645
643
  self._instance.connect(">", logger=Mock())
646
644
  self._instance.hardware_expect("dummy command", ">", Mock())
647
645
 
648
646
  def test_rsa_failure(self):
649
- pkey = paramiko.RSAKey.from_private_key(
650
- StringIO(KEY_WITH_PASSPHRASE), password=KEY_PASSPHRASE
651
- )
652
-
653
647
  server = SSHServer(user2key={})
654
648
 
655
649
  with self.assertRaises(SSHSessionException):
@@ -659,7 +653,8 @@ class TestSshSession(TestCase):
659
653
  "",
660
654
  port=server.port,
661
655
  on_session_start=self._on_session_start,
662
- pkey=pkey,
656
+ pkey=KEY_WITH_PASSPHRASE,
657
+ pkey_passphrase=KEY_PASSPHRASE,
663
658
  )
664
659
  self._instance.connect(">", logger=Mock())
665
660
  self._instance.hardware_expect("dummy command", ">", Mock())
@@ -0,0 +1,15 @@
1
+ import logging
2
+ import sys
3
+
4
+ import pytest
5
+
6
+
7
+ @pytest.fixture()
8
+ def logger():
9
+ return logging.getLogger(__file__)
10
+
11
+
12
+ def pytest_ignore_collect(path):
13
+ if sys.version_info.major == 2:
14
+ if str(path).endswith("_py3.py"):
15
+ return True
@@ -19,7 +19,7 @@ commands =
19
19
  pytest --cov=cloudshell.cli tests --cov-report=xml
20
20
 
21
21
  [testenv:pre-commit]
22
- basepython = python3
22
+ basepython = python3.9
23
23
  skip_install = true
24
24
  deps = pre-commit
25
25
  commands = pre-commit run --all-files --show-diff-on-failure
@@ -0,0 +1 @@
1
+ 4.1.1
@@ -1,5 +0,0 @@
1
- pre-commit
2
- tox
3
- tox-factor
4
- -r test_requirements.txt
5
- --extra-index-url https://test.pypi.org/simple -r requirements.txt
@@ -1 +0,0 @@
1
- 4.0.5
File without changes
File without changes