atomicshop 2.15.11__py3-none-any.whl → 3.10.5__py3-none-any.whl

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 (221) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
  3. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  4. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  5. atomicshop/a_mains/github_wrapper.py +11 -0
  6. atomicshop/a_mains/install_ca_certificate.py +172 -0
  7. atomicshop/a_mains/process_from_port.py +119 -0
  8. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  9. atomicshop/a_mains/update_config_toml.py +38 -0
  10. atomicshop/basics/ansi_escape_codes.py +3 -1
  11. atomicshop/basics/argparse_template.py +2 -0
  12. atomicshop/basics/booleans.py +27 -30
  13. atomicshop/basics/bytes_arrays.py +43 -0
  14. atomicshop/basics/classes.py +149 -1
  15. atomicshop/basics/enums.py +2 -2
  16. atomicshop/basics/exceptions.py +5 -1
  17. atomicshop/basics/list_of_classes.py +29 -0
  18. atomicshop/basics/multiprocesses.py +374 -50
  19. atomicshop/basics/strings.py +72 -3
  20. atomicshop/basics/threads.py +14 -0
  21. atomicshop/basics/tracebacks.py +13 -3
  22. atomicshop/certificates.py +153 -52
  23. atomicshop/config_init.py +11 -6
  24. atomicshop/console_user_response.py +7 -14
  25. atomicshop/consoles.py +9 -0
  26. atomicshop/datetimes.py +1 -1
  27. atomicshop/diff_check.py +3 -3
  28. atomicshop/dns.py +128 -3
  29. atomicshop/etws/_pywintrace_fix.py +17 -0
  30. atomicshop/etws/trace.py +40 -42
  31. atomicshop/etws/traces/trace_dns.py +56 -44
  32. atomicshop/etws/traces/trace_tcp.py +130 -0
  33. atomicshop/file_io/csvs.py +27 -5
  34. atomicshop/file_io/docxs.py +34 -17
  35. atomicshop/file_io/file_io.py +31 -17
  36. atomicshop/file_io/jsons.py +49 -0
  37. atomicshop/file_io/tomls.py +139 -0
  38. atomicshop/filesystem.py +616 -291
  39. atomicshop/get_process_list.py +3 -3
  40. atomicshop/http_parse.py +149 -93
  41. atomicshop/ip_addresses.py +6 -1
  42. atomicshop/mitm/centered_settings.py +132 -0
  43. atomicshop/mitm/config_static.py +207 -0
  44. atomicshop/mitm/config_toml_editor.py +55 -0
  45. atomicshop/mitm/connection_thread_worker.py +875 -357
  46. atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
  47. atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
  48. atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
  49. atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
  50. atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
  51. atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
  52. atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
  53. atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
  54. atomicshop/mitm/engines/create_module_template.py +58 -14
  55. atomicshop/mitm/import_config.py +359 -139
  56. atomicshop/mitm/initialize_engines.py +160 -80
  57. atomicshop/mitm/message.py +64 -23
  58. atomicshop/mitm/mitm_main.py +892 -0
  59. atomicshop/mitm/recs_files.py +183 -0
  60. atomicshop/mitm/shared_functions.py +4 -10
  61. atomicshop/mitm/ssh_tester.py +82 -0
  62. atomicshop/mitm/statistic_analyzer.py +136 -40
  63. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
  64. atomicshop/monitor/checks/dns.py +1 -1
  65. atomicshop/networks.py +671 -0
  66. atomicshop/on_exit.py +39 -9
  67. atomicshop/package_mains_processor.py +84 -0
  68. atomicshop/permissions/permissions.py +22 -0
  69. atomicshop/permissions/ubuntu_permissions.py +239 -0
  70. atomicshop/permissions/win_permissions.py +33 -0
  71. atomicshop/print_api.py +24 -42
  72. atomicshop/process.py +24 -6
  73. atomicshop/process_poller/process_pool.py +0 -1
  74. atomicshop/process_poller/simple_process_pool.py +204 -5
  75. atomicshop/python_file_patcher.py +1 -1
  76. atomicshop/python_functions.py +27 -75
  77. atomicshop/speech_recognize.py +8 -0
  78. atomicshop/ssh_remote.py +158 -172
  79. atomicshop/system_resource_monitor.py +61 -47
  80. atomicshop/system_resources.py +8 -8
  81. atomicshop/tempfiles.py +1 -2
  82. atomicshop/urls.py +6 -0
  83. atomicshop/venvs.py +28 -0
  84. atomicshop/versioning.py +27 -0
  85. atomicshop/web.py +98 -27
  86. atomicshop/web_apis/google_custom_search.py +44 -0
  87. atomicshop/web_apis/google_llm.py +188 -0
  88. atomicshop/websocket_parse.py +450 -0
  89. atomicshop/wrappers/certauthw/certauth.py +1 -0
  90. atomicshop/wrappers/cryptographyw.py +29 -8
  91. atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
  92. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
  93. atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
  94. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  95. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
  96. atomicshop/wrappers/ctyping/setup_device.py +466 -0
  97. atomicshop/wrappers/ctyping/win_console.py +39 -0
  98. atomicshop/wrappers/dockerw/dockerw.py +113 -2
  99. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  100. atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
  101. atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
  102. atomicshop/wrappers/factw/get_file_data.py +12 -5
  103. atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
  104. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
  105. atomicshop/wrappers/githubw.py +537 -54
  106. atomicshop/wrappers/loggingw/consts.py +1 -1
  107. atomicshop/wrappers/loggingw/filters.py +23 -0
  108. atomicshop/wrappers/loggingw/formatters.py +12 -0
  109. atomicshop/wrappers/loggingw/handlers.py +214 -107
  110. atomicshop/wrappers/loggingw/loggers.py +19 -0
  111. atomicshop/wrappers/loggingw/loggingw.py +860 -22
  112. atomicshop/wrappers/loggingw/reading.py +134 -112
  113. atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
  114. atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
  115. atomicshop/wrappers/netshw.py +271 -0
  116. atomicshop/wrappers/playwrightw/engine.py +34 -19
  117. atomicshop/wrappers/playwrightw/infra.py +5 -0
  118. atomicshop/wrappers/playwrightw/javascript.py +7 -3
  119. atomicshop/wrappers/playwrightw/keyboard.py +14 -0
  120. atomicshop/wrappers/playwrightw/scenarios.py +172 -5
  121. atomicshop/wrappers/playwrightw/waits.py +9 -7
  122. atomicshop/wrappers/powershell_networking.py +80 -0
  123. atomicshop/wrappers/psutilw/processes.py +37 -1
  124. atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
  125. atomicshop/wrappers/pyopensslw.py +9 -2
  126. atomicshop/wrappers/pywin32w/cert_store.py +116 -0
  127. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  128. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  129. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  130. atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
  131. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
  132. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
  133. atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
  134. atomicshop/wrappers/socketw/accepter.py +21 -7
  135. atomicshop/wrappers/socketw/certificator.py +216 -150
  136. atomicshop/wrappers/socketw/creator.py +190 -50
  137. atomicshop/wrappers/socketw/dns_server.py +491 -182
  138. atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
  139. atomicshop/wrappers/socketw/process_getter.py +86 -0
  140. atomicshop/wrappers/socketw/receiver.py +144 -102
  141. atomicshop/wrappers/socketw/sender.py +65 -35
  142. atomicshop/wrappers/socketw/sni.py +334 -165
  143. atomicshop/wrappers/socketw/socket_base.py +134 -0
  144. atomicshop/wrappers/socketw/socket_client.py +137 -95
  145. atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
  146. atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
  147. atomicshop/wrappers/socketw/ssl_base.py +15 -14
  148. atomicshop/wrappers/socketw/statistics_csv.py +148 -17
  149. atomicshop/wrappers/sysmonw.py +1 -1
  150. atomicshop/wrappers/ubuntu_terminal.py +65 -26
  151. atomicshop/wrappers/win_auditw.py +189 -0
  152. atomicshop/wrappers/winregw/__init__.py +0 -0
  153. atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
  154. atomicshop/wrappers/winregw/winreg_network.py +232 -0
  155. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
  156. atomicshop-3.10.5.dist-info/RECORD +306 -0
  157. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
  158. atomicshop/_basics_temp.py +0 -101
  159. atomicshop/a_installs/win/fibratus.py +0 -9
  160. atomicshop/a_installs/win/mongodb.py +0 -9
  161. atomicshop/a_installs/win/pycharm.py +0 -9
  162. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  163. atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
  164. atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
  165. atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
  166. atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
  167. atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
  168. atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
  169. atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
  170. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  171. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  172. atomicshop/addons/package_setup/Setup.cmd +0 -7
  173. atomicshop/archiver/_search_in_zip.py +0 -189
  174. atomicshop/archiver/archiver.py +0 -34
  175. atomicshop/archiver/search_in_archive.py +0 -250
  176. atomicshop/archiver/sevenz_app_w.py +0 -86
  177. atomicshop/archiver/sevenzs.py +0 -44
  178. atomicshop/archiver/zips.py +0 -293
  179. atomicshop/file_types.py +0 -24
  180. atomicshop/mitm/config_editor.py +0 -37
  181. atomicshop/mitm/engines/create_module_template_example.py +0 -13
  182. atomicshop/mitm/initialize_mitm_server.py +0 -268
  183. atomicshop/pbtkmultifile_argparse.py +0 -88
  184. atomicshop/permissions.py +0 -151
  185. atomicshop/script_as_string_processor.py +0 -38
  186. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  187. atomicshop/ssh_scripts/process_from_port.py +0 -27
  188. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  189. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  190. atomicshop/wrappers/dockerw/install_docker.py +0 -209
  191. atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
  192. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
  193. atomicshop/wrappers/ffmpegw.py +0 -125
  194. atomicshop/wrappers/fibratusw/install.py +0 -81
  195. atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
  196. atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
  197. atomicshop/wrappers/msiw.py +0 -149
  198. atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
  199. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  200. atomicshop/wrappers/psutilw/networks.py +0 -45
  201. atomicshop/wrappers/pycharmw.py +0 -81
  202. atomicshop/wrappers/socketw/base.py +0 -59
  203. atomicshop/wrappers/socketw/get_process.py +0 -107
  204. atomicshop/wrappers/wslw.py +0 -191
  205. atomicshop-2.15.11.dist-info/RECORD +0 -302
  206. /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
  207. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  208. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  209. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  210. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  211. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  212. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  213. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  214. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  215. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  216. /atomicshop/{archiver → permissions}/__init__.py +0 -0
  217. /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
  218. /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
  219. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  220. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
  221. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
atomicshop/ssh_remote.py CHANGED
@@ -1,19 +1,23 @@
1
1
  import sys
2
- import base64
3
- import socket
2
+ import logging
3
+ from pathlib import Path
4
+ import shlex
4
5
 
5
- from .print_api import print_api
6
- from .wrappers.loggingw import loggingw
7
- from .wrappers.socketw import base
8
-
9
-
10
- # External Libraries
11
6
  try:
12
7
  import paramiko
13
8
  except ImportError as exception_object:
14
9
  print(f"Library missing: {exception_object.name}. Install by executing: pip install paramiko")
15
10
  sys.exit()
16
11
 
12
+ from .print_api import print_api
13
+ from .wrappers.loggingw import loggingw
14
+ from .wrappers.socketw import socket_base
15
+
16
+
17
+ class SSHRemoteWrapperNoPythonFound(Exception):
18
+ """Raised when no usable Python 3 interpreter found on remote host."""
19
+ pass
20
+
17
21
 
18
22
  class SSHRemote:
19
23
  """
@@ -87,9 +91,13 @@ class SSHRemote:
87
91
  sys.exit(main())
88
92
 
89
93
  """
90
- logger = loggingw.get_logger_with_level("network." + __name__.rpartition('.')[2])
91
-
92
- def __init__(self, ip_address: str, username: str, password: str):
94
+ def __init__(
95
+ self,
96
+ ip_address: str,
97
+ username: str,
98
+ password: str,
99
+ logger: logging.Logger = None
100
+ ):
93
101
  self.ip_address: str = ip_address
94
102
  self.username: str = username
95
103
  self.password: str = password
@@ -97,11 +105,23 @@ class SSHRemote:
97
105
  # Initializing paramiko SSHClient class
98
106
  self.ssh_client = paramiko.SSHClient()
99
107
 
100
- def connect(self):
108
+ # Variable to store detected python command on remote (python3 / python).
109
+ self.python_cmd: str | None = None
110
+
111
+ if logger:
112
+ # Create child logger for the provided logger with the module's name.
113
+ self.logger: logging.Logger = loggingw.get_logger_with_level(f'{logger.name}.{Path(__file__).stem}')
114
+ else:
115
+ self.logger: logging.Logger = logger
116
+
117
+ def connect(
118
+ self,
119
+ timeout: int = 60
120
+ ):
101
121
  error: str = str()
102
122
 
103
123
  # Get all local interfaces IPv4 addresses.
104
- local_interfaces_ipv4 = base.get_local_network_interfaces_ip_address("ipv4", True)
124
+ local_interfaces_ipv4 = socket_base.get_local_network_interfaces_ip_address("ipv4", True)
105
125
  # Check if the target IP address is in the list of local interfaces.
106
126
  if self.ip_address in local_interfaces_ipv4:
107
127
  # If it is, we don't need to connect to it via SSH, it means that we want to connect to ourselves.
@@ -119,75 +139,15 @@ class SSHRemote:
119
139
  # with description of
120
140
  # Server 'address_goes_here' not found in known_hosts
121
141
  self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
122
- try:
123
- # Executing SSH connection to client.
124
- self.ssh_client.connect(self.ip_address, username=self.username, password=self.password, timeout=60)
125
- # When port 22 is unreachable on the client.
126
- except paramiko.ssh_exception.NoValidConnectionsError as e:
127
- error = str(e)
128
- # Logging the error also. Since the process name isn't critical, we'll continue script execution.
129
- print_api(error, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
130
- pass
131
- except paramiko.ssh_exception.SSHException as e:
132
- error = str(e)
133
- # Logging the error also. Since the process name isn't critical, we'll continue script execution.
134
- print_api(error, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
135
- pass
136
- except ConnectionResetError:
137
- # Returning the error.
138
- error = "An existing connection was forcibly closed by the remote host."
139
- # Logging the error also. Since the process name isn't critical, we'll continue script execution.
140
- print_api(error, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
141
- pass
142
- except TimeoutError:
143
- # Returning the error.
144
- error = "Connection timed out."
145
- # Logging the error also. Since the process name isn't critical, we'll continue script execution.
146
- print_api(error, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
147
- pass
142
+
143
+ # Executing SSH connection to client.
144
+ self.ssh_client.connect(self.ip_address, username=self.username, password=self.password, timeout=timeout)
148
145
 
149
146
  return error
150
147
 
151
148
  def close(self):
152
149
  self.ssh_client.close()
153
150
 
154
- def exec_command_with_error_handling(self, script_string: str):
155
- # Defining variables.
156
- stdin = None
157
- stdout = None
158
- stderr = None
159
- result_exception = None
160
-
161
- # Don't put debugging break point over the next line in PyCharm. For some reason it gets stuck.
162
- # Put the point right after that.
163
- try:
164
- stdin, stdout, stderr = self.ssh_client.exec_command(command=script_string, timeout=30)
165
- except AttributeError as function_exception_object:
166
- if function_exception_object.name == "open_session":
167
- result_exception = "'SSHRemote().connect' wasn't executed."
168
- print_api(result_exception, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
169
-
170
- # Since getting Process name is not the main feature of the server, we can pass the exception
171
- pass
172
- else:
173
- result_exception = f"Couldn't execute script over SSH. Unknown yet exception with 'AttributeError' " \
174
- f"and name: {function_exception_object.name}"
175
- print_api(result_exception, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
176
- # Since getting Process name is not the main feature of the server, we can pass the exception
177
- pass
178
- except socket.error:
179
- result_exception = "Couldn't execute script over SSH. SSH socket closed."
180
- print_api(result_exception, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
181
- # Since getting Process name is not the main feature of the server, we can pass the exception
182
- pass
183
- except Exception:
184
- result_exception = "Couldn't execute script over SSH. Unknown yet exception."
185
- print_api(result_exception, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
186
- # Since getting Process name is not the main feature of the server, we can pass the exception
187
- pass
188
-
189
- return stdin, stdout, stderr, result_exception
190
-
191
151
  @staticmethod
192
152
  def check_console_output_for_errors(console_output_string: str):
193
153
  # Defining variables.
@@ -202,104 +162,132 @@ class SSHRemote:
202
162
  if "ModuleNotFoundError: No module named" in line:
203
163
  function_result = f"Python library is not installed - {line}"
204
164
  break
165
+ else:
166
+ function_result = console_output_string
205
167
 
206
168
  return function_result
207
169
 
208
- def remote_execution(self, script_string: str):
209
- # Defining variables.
210
- output_lines = None
211
- function_error = None
212
- stdin = None
213
- stdout = None
214
- stderr = None
215
- exec_exception = None
170
+ def remote_execution(
171
+ self,
172
+ command: str,
173
+ script_string: str = None
174
+ ) -> tuple[str, str]:
175
+ """
176
+ Function to execute any command over SSH.
177
+
178
+ :param command: command to execute over SSH.
179
+ :param script_string: string representation of script to execute as input.
180
+ Example:
181
+ command = "python - 56734"
182
+ script_string = "import sys;print(f'sys.argv[0]')"
183
+
184
+ Under ssh in terminal it would execute like this:
185
+ ssh User@HostIpv4
186
+
187
+ python - 56734 << EOF
188
+ import sys;print(f'sys.argv[0]')
189
+ EOF
190
+
191
+ Or as onliner:
192
+ ssh User@HostIpv4 "python - 56734 << EOF import sys;print(f'sys.argv[0]') EOF"
193
+
194
+ or using a specific file path:
195
+ ssh User@HostIpv4 "python - 56734" < /path/to/script.py
196
+
197
+ :return: SSH console output, Error output
198
+ """
199
+ output_result: str = str()
200
+ error_result: str = str()
216
201
 
217
202
  # Execute the command over SSH remotely.
218
- stdin, stdout, stderr, exec_exception = self.exec_command_with_error_handling(script_string)
219
- # If exception was returned from execution.
220
- if exec_exception:
221
- self.logger.info("Trying to reconnect over SSH.")
222
- # Close existing SSH Connection.
223
- self.close()
224
- # And connect again.
225
- self.connect()
226
- self.logger.info("Reconnected. Trying to send the command one more time.")
227
- # Try to execute the command over SSH remotely again.
228
- stdin, stdout, stderr, exec_exception = self.exec_command_with_error_handling(script_string)
229
- # If there was an exception again.
230
- if exec_exception:
231
- # Populate the function_error variable that will be returned outside.
232
- function_error = exec_exception
233
-
234
- # If there was no exception executing the remote command.
235
- if not function_error:
236
- # Reading the buffer of stdout.
237
- output_lines = stdout.readlines()
238
- # Reading the buffer of stderr.
239
- function_error = stderr.readlines()
240
-
241
- # Joining error lines list to string if not empty.
242
- if function_error:
243
- function_error = ''.join(function_error)
244
- # Else, joining output lines to string.
245
- else:
246
- output_lines = ''.join(output_lines)
203
+ stdin, stdout, stderr = self.ssh_client.exec_command(command=command, timeout=30)
204
+
205
+ # Writing the script string into stdin buffer.
206
+ if script_string:
207
+ stdin.write(script_string)
208
+ stdin.channel.shutdown_write()
247
209
 
248
- # Since they're "file-like" objects we need to close them after we finished using.
249
- stdin.close()
250
- stdout.close()
251
- stderr.close()
210
+ # Reading the buffer of stdout.
211
+ output_lines: list = stdout.readlines()
212
+ # Reading the buffer of stderr.
213
+ error_lines: list = stderr.readlines()
252
214
 
253
- return output_lines, function_error
215
+ # Joining error lines list to string if not empty.
216
+ if error_lines:
217
+ error_result: str = ''.join(error_lines)
218
+ # Else, joining output lines to string.
219
+ else:
220
+ output_result = ''.join(output_lines)
254
221
 
255
- def remote_execution_python(self, script_string: str):
222
+ # Since they're "file-like" objects we need to close them after we finished using.
223
+ stdin.close()
224
+ stdout.close()
225
+ stderr.close()
226
+
227
+ return output_result, error_result
228
+
229
+ def _detect_remote_python_cmd_name(self) -> str:
230
+ """
231
+ Try 'python3' then 'python' on the remote, return the one that is Python 3.
232
+ Raises if neither works.
233
+ """
234
+ for candidate in ("python3", "python"):
235
+ # Use a simple version check that works on both Windows and Linux
236
+ cmd = f'{candidate} -c "import sys; print(sys.version_info[0])"'
237
+ stdin, stdout, stderr = self.ssh_client.exec_command(cmd, timeout=5)
238
+
239
+ out = stdout.read().decode().strip()
240
+ exit_status = stdout.channel.recv_exit_status()
241
+
242
+ if exit_status == 0 and out == "3":
243
+ print_api(f"Detected remote Python 3 interpreter (once per client port): {candidate}", logger=self.logger)
244
+ return candidate
245
+
246
+ raise SSHRemoteWrapperNoPythonFound("No usable Python 3 interpreter found on remote host")
247
+
248
+ def _get_python_cmd(self) -> str:
249
+ if self.python_cmd is None:
250
+ self.python_cmd = self._detect_remote_python_cmd_name()
251
+ return self.python_cmd
252
+
253
+ def remote_execution_python(
254
+ self,
255
+ script_string: str,
256
+ script_arg_values: tuple = None,
257
+ script_kwargs: dict = None,
258
+ ):
256
259
  """
257
260
  Function to execute python script over SSH.
258
261
 
259
- Example:
260
- network.logger.info("Initializing SSH connection to get the calling process.")
261
-
262
- # Initializing SSHRemote class.
263
- ssh_client = SSHRemote(ip_address=client_message.client_ip, username=username, password=password)
264
- # Making actual SSH Connection to the computer.
265
- ssh_connection_error = ssh_client.connect()
266
- # If there's an exception / error during connection.
267
- if ssh_connection_error:
268
- # Put the error in the process name value.
269
- client_message.process_name = ssh_connection_error
270
- else:
271
- # If no error, then initialize the variables for python script execution over SSH.
272
- remote_output = remote_error = None
273
-
274
- # Put source port variable inside the string script.
275
- script_string: str = \
276
- put_variable_into_string_script(ssh_script_port_by_process_string, client_message.source_port)
277
-
278
- # Execute the python script on remote computer over SSH.
279
- remote_output, remote_error = ssh_client.remote_execution_python(script_string)
280
-
281
- # If there was an error during execution, put it in process name.
282
- if remote_error:
283
- client_message.process_name = remote_error
284
- # If there was no error during execution, put the output of the ssh to process name.
285
- else:
286
- client_message.process_name = remote_output
287
- network.logger.info(f"Remote SSH: Client executing Command Line: {client_message.process_name}")
288
-
289
262
  :param script_string: string representation of python script.
263
+ :param script_arg_values: values arguments to pass to the script. Example for first argument: 56734
264
+ :param script_kwargs: keyword arguments to pass to the script.
265
+ Example: {'-r': None}
266
+ Interpreted as: -r
267
+ Example: {'-f': 'value'}
268
+ Interpreted as: -f value
269
+ Example: {'--arg': value}
270
+ Interpreted as: --arg value
271
+
290
272
  :return: SSH console output, Error output
291
273
  """
292
274
  # Defining variables.
293
- function_return = None
294
- function_error = None
275
+ error_result: str | None = None
276
+
277
+ python_cmd = self._get_python_cmd()
278
+ command: str = f"{python_cmd} -"
295
279
 
296
- encoded_base64_string = base64.b64encode(script_string.encode('ascii'))
297
- command_string: str = fr'python -c "import base64;exec(base64.b64decode({encoded_base64_string}))"'
280
+ if script_arg_values:
281
+ for arg in script_arg_values:
282
+ command += " " + shlex.quote(str(arg))
298
283
 
299
- # remote_output, remote_error = ssh_client.remote_execution('ipconfig')
300
- # remote_output, remote_error = ssh_client.remote_execution("python -c print('Hello')")
301
- # remote_output, remote_error = ssh_client.remote_execution("python -c import psutil")
302
- remote_output, remote_error = self.remote_execution(command_string)
284
+ if script_kwargs:
285
+ for key, value in script_kwargs.items():
286
+ command += f" {shlex.quote(str(key))}"
287
+ if value is not None:
288
+ command += " " + shlex.quote(str(value))
289
+
290
+ remote_output, remote_error = self.remote_execution(command=command, script_string=script_string)
303
291
 
304
292
  # If there was an error during remote execution
305
293
  if remote_error:
@@ -308,37 +296,35 @@ class SSHRemote:
308
296
  # If the message is known and didn't return empty.
309
297
  if console_check:
310
298
  # 'execution_error' variable will be that full error.
311
- function_error = console_check
299
+ error_result = console_check
300
+ else:
301
+ error_result = remote_error
312
302
 
313
- return remote_output, function_error
303
+ return remote_output, error_result
314
304
 
315
- def connect_get_client_commandline(self, script_string):
305
+ def connect_get_client_commandline(
306
+ self,
307
+ port: int,
308
+ script_string: str):
316
309
  # Defining locals.
317
- execution_output = None
318
- execution_error = None
310
+ execution_output: str | None = None
319
311
 
320
312
  # Making actual SSH Connection to the computer.
321
313
  execution_error = self.connect()
322
314
  # if there was an error, try to connect again.
323
315
  if execution_error:
324
- self.logger.info("Retrying SSH Connection Initialization.")
316
+ print_api("Retrying SSH Connection Initialization.", logger=self.logger, logger_method='info')
325
317
  execution_error = self.connect()
326
318
 
327
319
  # If there was still an error, we won't be executing the script. And the error will be passed to
328
320
  # 'process_name'.
329
321
  if not execution_error:
330
- self.logger.info("Executing SSH command to acquire the calling process.")
322
+ print_api("Executing SSH command to acquire the calling process.", logger=self.logger, logger_method='info')
331
323
 
332
- try:
333
- execution_output, execution_error = self.remote_execution_python(script_string)
334
- # Basically we don't care much about SSH exceptions. Just log them and pass to record.
335
- except Exception as function_exception_object:
336
- execution_error = function_exception_object
337
- print_api(execution_error, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
338
- pass
324
+ execution_output, execution_error = self.remote_execution_python(script_string=script_string, script_arg_values=(str(port),))
339
325
 
340
326
  # Closing SSH connection at this stage.
341
327
  self.close()
342
- self.logger.info("Acquired. Closed SSH connection.")
328
+ print_api("Acquired. Closed SSH connection.", logger=self.logger, logger_method='info')
343
329
 
344
330
  return execution_output, execution_error
@@ -2,8 +2,7 @@ from typing import Union
2
2
  import threading
3
3
  import multiprocessing.managers
4
4
 
5
- from .print_api import print_api
6
- from . import system_resources
5
+ from . import system_resources, print_api
7
6
 
8
7
 
9
8
  class SystemResourceMonitor:
@@ -109,67 +108,77 @@ class SystemResourceMonitor:
109
108
  self.thread: Union[threading.Thread, None] = None
110
109
  # Sets the running state of the monitoring process. Needed to stop the monitoring and queue threads.
111
110
  self.running: bool = False
112
- # The shared results dictionary.
111
+ # The shared results' dictionary.
113
112
  self.results: dict = {}
114
113
 
115
- def start(self, print_kwargs: dict = None):
114
+ def start(
115
+ self,
116
+ print_kwargs: dict = None,
117
+ thread_as_daemon: bool = True
118
+ ):
116
119
  """
117
120
  Start the monitoring process.
118
121
  :param print_kwargs:
122
+ :param thread_as_daemon: bool, set the thread as daemon. If you're running the monitoring process in the main
123
+ process, set it to True. If you're running the monitoring process in a separate process, set it to False.
124
+ In child processes created by multiprocessing.Process, the thread works differently. You might not
125
+ get the desired result.
119
126
  :return:
120
127
  """
121
128
 
122
- def run_check_system_resources(
123
- interval, get_cpu, get_memory, get_disk_io_bytes, get_disk_files_count, get_disk_busy_time,
124
- get_disk_used_percent, calculate_maximum_changed_disk_io, maximum_disk_io, queue_list, manager_dict):
125
- """
126
- Continuously update the system resources in the shared results dictionary.
127
- This function runs in a separate process.
128
- """
129
-
130
- while self.running:
131
- # Get the results of the system resources check function and store them in temporary results dictionary.
132
- results = system_resources.check_system_resources(
133
- interval=interval, get_cpu=get_cpu, get_memory=get_memory,
134
- get_disk_io_bytes=get_disk_io_bytes, get_disk_files_count=get_disk_files_count,
135
- get_disk_busy_time=get_disk_busy_time, get_disk_used_percent=get_disk_used_percent)
136
-
137
- if calculate_maximum_changed_disk_io:
138
- if results['disk_io_read'] > maximum_disk_io['read_bytes_per_sec']:
139
- maximum_disk_io['read_bytes_per_sec'] = results['disk_io_read']
140
- if results['disk_io_write'] > maximum_disk_io['write_bytes_per_sec']:
141
- maximum_disk_io['write_bytes_per_sec'] = results['disk_io_write']
142
- if results['disk_files_count_read'] > maximum_disk_io['read_files_count_per_sec']:
143
- maximum_disk_io['read_files_count_per_sec'] = results['disk_files_count_read']
144
- if results['disk_files_count_write'] > maximum_disk_io['write_files_count_per_sec']:
145
- maximum_disk_io['write_files_count_per_sec'] = results['disk_files_count_write']
146
- results['maximum_disk_io'] = maximum_disk_io
147
-
148
- if queue_list is not None:
149
- for queue in queue_list:
150
- queue.put(results)
151
-
152
- # Update the shared results dictionary with the temporary results dictionary.
153
- # This is done in separate steps to avoid overwriting the special 'multiprocessing.Manager.dict' object.
154
- # So we update the shared results dictionary with the temporary results dictionary.
155
- if manager_dict is not None:
156
- manager_dict.update(results)
157
-
158
- self.results = results
159
-
160
129
  if print_kwargs is None:
161
130
  print_kwargs = {}
162
131
 
163
132
  if self.thread is None:
164
133
  self.running = True
165
- self.thread = threading.Thread(target=run_check_system_resources, args=(
134
+ self.thread = threading.Thread(target=self.run_check_system_resources, args=(
166
135
  self.interval, self.get_cpu, self.get_memory, self.get_disk_io_bytes, self.get_disk_files_count,
167
136
  self.get_disk_busy_time, self.get_disk_used_percent, self.calculate_maximum_changed_disk_io,
168
137
  self.maximum_disk_io, self.queue_list, self.manager_dict))
169
- self.thread.daemon = True
138
+ self.thread.daemon = thread_as_daemon
170
139
  self.thread.start()
171
140
  else:
172
- print_api("Monitoring is already running.", color='yellow', **print_kwargs)
141
+ print_api.print_api("Monitoring is already running.", color='yellow', **print_kwargs)
142
+
143
+ def run_check_system_resources(
144
+ self,
145
+ interval, get_cpu, get_memory, get_disk_io_bytes, get_disk_files_count, get_disk_busy_time,
146
+ get_disk_used_percent, calculate_maximum_changed_disk_io, maximum_disk_io, queue_list, manager_dict):
147
+ """
148
+ Continuously update the system resources in the shared results dictionary.
149
+ This function runs in a separate process.
150
+ """
151
+
152
+ while self.running:
153
+ # Get the results of the system resources check function and store them in
154
+ # temporary results' dictionary.
155
+ results = system_resources.check_system_resources(
156
+ interval=interval, get_cpu=get_cpu, get_memory=get_memory,
157
+ get_disk_io_bytes=get_disk_io_bytes, get_disk_files_count=get_disk_files_count,
158
+ get_disk_busy_time=get_disk_busy_time, get_disk_used_percent=get_disk_used_percent)
159
+
160
+ if calculate_maximum_changed_disk_io:
161
+ if results['disk_io_read'] > maximum_disk_io['read_bytes_per_sec']:
162
+ maximum_disk_io['read_bytes_per_sec'] = results['disk_io_read']
163
+ if results['disk_io_write'] > maximum_disk_io['write_bytes_per_sec']:
164
+ maximum_disk_io['write_bytes_per_sec'] = results['disk_io_write']
165
+ if results['disk_files_count_read'] > maximum_disk_io['read_files_count_per_sec']:
166
+ maximum_disk_io['read_files_count_per_sec'] = results['disk_files_count_read']
167
+ if results['disk_files_count_write'] > maximum_disk_io['write_files_count_per_sec']:
168
+ maximum_disk_io['write_files_count_per_sec'] = results['disk_files_count_write']
169
+ results['maximum_disk_io'] = maximum_disk_io
170
+
171
+ if queue_list is not None:
172
+ for queue in queue_list:
173
+ queue.put(results)
174
+
175
+ # Update the shared results dictionary with the temporary results' dictionary.
176
+ # This is done in separate steps to avoid overwriting the special 'multiprocessing.Manager.dict' object.
177
+ # So we update the shared results dictionary with the temporary results' dictionary.
178
+ if manager_dict is not None:
179
+ manager_dict.update(results)
180
+
181
+ self.results = results
173
182
 
174
183
  def get_results(self) -> dict:
175
184
  """
@@ -204,6 +213,7 @@ def start_monitoring(
204
213
  calculate_maximum_changed_disk_io: bool = False,
205
214
  queue_list: list = None,
206
215
  manager_dict: multiprocessing.managers.DictProxy = None, # multiprocessing.Manager().dict()
216
+ get_results_thread_as_daemon: bool = True,
207
217
  print_kwargs: dict = None
208
218
  ):
209
219
  """
@@ -234,6 +244,10 @@ def start_monitoring(
234
244
  multiprocessing.Process(
235
245
  target=system_resource_monitor.start_monitoring, kwargs={'manager_dict': shared_dict}).start()
236
246
 
247
+ :param get_results_thread_as_daemon: bool, set the thread as daemon. If you're running the monitoring process in the
248
+ main process, set it to True. If you're running the monitoring process in a separate process, set it to False.
249
+ In child processes created by multiprocessing.Process, the thread works differently.
250
+ You might not get the desired result.
237
251
  :param print_kwargs: dict, print kwargs.
238
252
  :return:
239
253
  """
@@ -256,9 +270,9 @@ def start_monitoring(
256
270
  queue_list=queue_list,
257
271
  manager_dict=manager_dict
258
272
  )
259
- SYSTEM_RESOURCES_MONITOR.start()
273
+ SYSTEM_RESOURCES_MONITOR.start(thread_as_daemon=get_results_thread_as_daemon)
260
274
  else:
261
- print_api("System resources monitoring is already running.", color='yellow', **(print_kwargs or {}))
275
+ print_api.print_api("System resources monitoring is already running.", color='yellow', **(print_kwargs or {}))
262
276
 
263
277
 
264
278
  def stop_monitoring():
@@ -5,9 +5,8 @@ import shutil
5
5
  import threading
6
6
  import multiprocessing.managers
7
7
 
8
- from .print_api import print_api
9
8
  from .wrappers.psutilw import cpus, memories, disks
10
- from . import system_resource_monitor
9
+ from . import system_resource_monitor, print_api
11
10
 
12
11
 
13
12
  def check_system_resources(
@@ -129,13 +128,13 @@ def wait_for_resource_availability(
129
128
 
130
129
  if result['cpu_usage'] < cpu_percent_max and result['memory_usage'] < memory_percent_max:
131
130
  break
132
- print_api(
131
+ print_api.print_api(
133
132
  f"Waiting for resources to be available... "
134
133
  f"CPU: {result['cpu_usage']}%, Memory: {result['memory_usage']}%", color='yellow')
135
134
  time.sleep(wait_time) # Wait for 'wait_time' seconds before checking again
136
135
 
137
136
 
138
- def test_disk_speed_with_monitoring(
137
+ def _test_disk_speed_with_monitoring(
139
138
  file_settings: list[dict],
140
139
  remove_file_after_each_copy: bool = False,
141
140
  target_directory=None,
@@ -144,6 +143,7 @@ def test_disk_speed_with_monitoring(
144
143
  print_kwargs: dict = None
145
144
  ):
146
145
  """
146
+ THIS IS NOT TESTED.
147
147
  Generates files and performs write and read operations in the specified target directory,
148
148
  while monitoring disk I/O speeds in a separate thread. Returns the maximum read and write rates,
149
149
  and the total operation time.
@@ -171,7 +171,7 @@ def test_disk_speed_with_monitoring(
171
171
  if monitoring:
172
172
  system_resource_monitor.start_monitoring(
173
173
  interval=1, get_cpu=False, get_memory=False, get_disk_io_bytes=True, get_disk_used_percent=False,
174
- get_disk_files_count=True, calculate_maximum_changed_disk_io=True, use_queue=True)
174
+ get_disk_files_count=True, calculate_maximum_changed_disk_io=True)
175
175
 
176
176
  if target_directory is None:
177
177
  target_directory = tempfile.mkdtemp()
@@ -196,7 +196,7 @@ def test_disk_speed_with_monitoring(
196
196
  shutil.copy(src_file_path, dest_directory)
197
197
 
198
198
  target_file_path = os.path.join(dest_directory, os.path.basename(src_file_path))
199
- print_api(f"Copied: {target_file_path}", **(print_kwargs or {}))
199
+ print_api.print_api(f"Copied: {target_file_path}", **(print_kwargs or {}))
200
200
 
201
201
  # Measure read speed.
202
202
  with open(target_file_path, "rb") as file:
@@ -212,7 +212,7 @@ def test_disk_speed_with_monitoring(
212
212
 
213
213
  overall_end_time = time.time()
214
214
  total_execution_time = overall_end_time - overall_start_time
215
- print_api(f"Total execution time: {total_execution_time}", **(print_kwargs or {}))
215
+ print_api.print_api(f"Total execution time: {total_execution_time}", **(print_kwargs or {}))
216
216
 
217
217
  # Cleanup. Remove all created files and directories.
218
218
  shutil.rmtree(source_directory)
@@ -220,7 +220,7 @@ def test_disk_speed_with_monitoring(
220
220
 
221
221
  if monitoring:
222
222
  # Stop the I/O monitoring.
223
- max_io_changes = system_resource_monitor.get_result()['maximum_disk_io']
223
+ max_io_changes = system_resource_monitor.get_results()['maximum_disk_io']
224
224
  system_resource_monitor.stop_monitoring()
225
225
 
226
226
  return total_execution_time, max_io_changes
atomicshop/tempfiles.py CHANGED
@@ -1,4 +1,3 @@
1
- # v1.0.3 - 01.04.2021 - 15:30
2
1
  import os
3
2
  import datetime
4
3
  import tempfile
@@ -59,4 +58,4 @@ class TempFile:
59
58
  self.file_path: str = self.directory + os.sep + self.file_name
60
59
 
61
60
  def remove(self):
62
- os.remove(self.file_path)
61
+ os.remove(self.file_path)