thestage 0.6.2__py3-none-any.whl → 0.6.3__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.
- thestage/.env +4 -5
- thestage/__init__.py +3 -3
- thestage/__main__.py +9 -9
- thestage/cli_command.py +56 -56
- thestage/cli_command_helper.py +51 -51
- thestage/color_scheme/color_scheme.py +7 -7
- thestage/config/__init__.py +18 -18
- thestage/config/config_storage.py +5 -5
- thestage/config/env_base.py +7 -7
- thestage/controllers/__init__.py +0 -0
- thestage/controllers/base_controller.py +67 -67
- thestage/controllers/config_controller.py +137 -137
- thestage/controllers/container_controller.py +389 -389
- thestage/controllers/instance_controller.py +183 -183
- thestage/controllers/project_controller.py +810 -810
- thestage/controllers/utils_controller.py +32 -32
- thestage/debug_main.dist.py +28 -28
- thestage/entities/__init__.py +0 -0
- thestage/entities/container.py +17 -17
- thestage/entities/enums/__init__.py +0 -0
- thestage/entities/enums/order_direction_type.py +6 -6
- thestage/entities/enums/shell_type.py +7 -7
- thestage/entities/enums/tail_output_type.py +6 -6
- thestage/entities/enums/yes_no_response.py +7 -7
- thestage/entities/file_item.py +27 -27
- thestage/entities/project_inference_simulator.py +18 -18
- thestage/entities/project_inference_simulator_model.py +16 -16
- thestage/entities/project_task.py +19 -19
- thestage/entities/rented_instance.py +19 -19
- thestage/entities/self_hosted_instance.py +18 -18
- thestage/exceptions/__init__.py +0 -0
- thestage/exceptions/auth_exception.py +6 -6
- thestage/exceptions/base_exception.py +13 -13
- thestage/exceptions/business_logic_exception.py +6 -6
- thestage/exceptions/config_exception.py +6 -6
- thestage/exceptions/file_system_exception.py +6 -6
- thestage/exceptions/git_access_exception.py +17 -17
- thestage/exceptions/remote_server_exception.py +24 -24
- thestage/git/ProgressPrinter.py +22 -22
- thestage/helpers/__init__.py +0 -0
- thestage/helpers/error_handler.py +115 -115
- thestage/helpers/exception_hook.py +14 -14
- thestage/helpers/logger/__init__.py +0 -0
- thestage/helpers/logger/app_logger.py +50 -50
- thestage/helpers/ssh_util.py +38 -38
- thestage/i18n/en_GB/messages.po +947 -947
- thestage/i18n/translation.py +9 -9
- thestage/main.py +36 -36
- thestage/services/.env +6 -6
- thestage/services/__init__.py +0 -0
- thestage/services/abstract_mapper.py +9 -9
- thestage/services/abstract_service.py +87 -87
- thestage/services/app_config_service.py +52 -52
- thestage/services/clients/__init__.py +0 -0
- thestage/services/clients/git/__init__.py +0 -0
- thestage/services/clients/git/git_client.py +436 -436
- thestage/services/clients/thestage_api/__init__.py +0 -0
- thestage/services/clients/thestage_api/api_client.py +718 -718
- thestage/services/clients/thestage_api/core/api_client_core.py +108 -108
- thestage/services/clients/thestage_api/core/http_client_exception.py +12 -12
- thestage/services/clients/thestage_api/dtos/__init__.py +0 -0
- thestage/services/clients/thestage_api/dtos/base_response.py +13 -13
- thestage/services/clients/thestage_api/dtos/cloud_provider_region.py +19 -19
- thestage/services/clients/thestage_api/dtos/container_param_request.py +11 -11
- thestage/services/clients/thestage_api/dtos/container_response.py +67 -67
- thestage/services/clients/thestage_api/dtos/docker_container_assigned_device.py +10 -10
- thestage/services/clients/thestage_api/dtos/docker_container_controller/docker_container_list_request.py +13 -13
- thestage/services/clients/thestage_api/dtos/docker_container_controller/docker_container_list_response.py +13 -13
- thestage/services/clients/thestage_api/dtos/docker_container_mapping.py +10 -10
- thestage/services/clients/thestage_api/dtos/entity_filter_request.py +14 -14
- thestage/services/clients/thestage_api/dtos/enums/__init__.py +0 -0
- thestage/services/clients/thestage_api/dtos/enums/container_pending_action.py +10 -10
- thestage/services/clients/thestage_api/dtos/enums/container_status.py +17 -17
- thestage/services/clients/thestage_api/dtos/enums/cpu_type.py +8 -8
- thestage/services/clients/thestage_api/dtos/enums/currency_type.py +10 -10
- thestage/services/clients/thestage_api/dtos/enums/daemon_status.py +9 -9
- thestage/services/clients/thestage_api/dtos/enums/disk_type.py +7 -7
- thestage/services/clients/thestage_api/dtos/enums/drive_type.py +7 -7
- thestage/services/clients/thestage_api/dtos/enums/gpu_name.py +8 -8
- thestage/services/clients/thestage_api/dtos/enums/inference_model_status.py +9 -9
- thestage/services/clients/thestage_api/dtos/enums/inference_simulator_status.py +15 -15
- thestage/services/clients/thestage_api/dtos/enums/instance_rented_status.py +17 -17
- thestage/services/clients/thestage_api/dtos/enums/instance_type.py +7 -7
- thestage/services/clients/thestage_api/dtos/enums/location_region.py +11 -11
- thestage/services/clients/thestage_api/dtos/enums/power_status.py +10 -10
- thestage/services/clients/thestage_api/dtos/enums/provider_name.py +11 -11
- thestage/services/clients/thestage_api/dtos/enums/selfhosted_status.py +10 -10
- thestage/services/clients/thestage_api/dtos/enums/task_execution_status.py +12 -12
- thestage/services/clients/thestage_api/dtos/enums/task_status.py +12 -12
- thestage/services/clients/thestage_api/dtos/frontend_status.py +10 -10
- thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_instance_request.py +13 -13
- thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_instance_response.py +13 -13
- thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_sagemaker_request.py +12 -12
- thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_sagemaker_response.py +12 -12
- thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_request.py +10 -10
- thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_response.py +13 -13
- thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_list_for_project_request.py +14 -14
- thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_list_for_project_response.py +12 -12
- thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_model_list_for_project_request.py +12 -12
- thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_model_list_for_project_response.py +13 -13
- thestage/services/clients/thestage_api/dtos/inference_simulator_model_response.py +11 -11
- thestage/services/clients/thestage_api/dtos/inference_simulator_response.py +11 -11
- thestage/services/clients/thestage_api/dtos/installed_service.py +17 -17
- thestage/services/clients/thestage_api/dtos/instance_detected_gpus.py +20 -20
- thestage/services/clients/thestage_api/dtos/instance_rented_response.py +71 -71
- thestage/services/clients/thestage_api/dtos/logging_controller/docker_container_log_stream_request.py +7 -7
- thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_request.py +13 -13
- thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_response.py +14 -14
- thestage/services/clients/thestage_api/dtos/logging_controller/task_log_stream_request.py +7 -7
- thestage/services/clients/thestage_api/dtos/logging_controller/user_logs_query_request.py +21 -21
- thestage/services/clients/thestage_api/dtos/logging_controller/user_logs_query_response.py +14 -14
- thestage/services/clients/thestage_api/dtos/paginated_entity_list.py +11 -11
- thestage/services/clients/thestage_api/dtos/pagination_data.py +10 -10
- thestage/services/clients/thestage_api/dtos/price_definition.py +14 -14
- thestage/services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_request.py +7 -7
- thestage/services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_response.py +10 -10
- thestage/services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_request.py +8 -8
- thestage/services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_response.py +6 -6
- thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_request.py +15 -15
- thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_response.py +10 -10
- thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_request.py +13 -13
- thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_response.py +10 -10
- thestage/services/clients/thestage_api/dtos/project_response.py +32 -32
- thestage/services/clients/thestage_api/dtos/selfhosted_instance_response.py +56 -56
- thestage/services/clients/thestage_api/dtos/sftp_path_helper.py +13 -13
- thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_key_to_user_request.py +8 -8
- thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_key_to_user_response.py +11 -11
- thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_public_key_to_instance_request.py +8 -8
- thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_public_key_to_instance_response.py +11 -11
- thestage/services/clients/thestage_api/dtos/ssh_key_controller/is_user_has_public_ssh_key_request.py +7 -7
- thestage/services/clients/thestage_api/dtos/ssh_key_controller/is_user_has_public_ssh_key_response.py +12 -12
- thestage/services/clients/thestage_api/dtos/task_controller/task_list_for_project_request.py +10 -10
- thestage/services/clients/thestage_api/dtos/task_controller/task_list_for_project_response.py +12 -12
- thestage/services/clients/thestage_api/dtos/task_controller/task_status_localized_map_response.py +9 -9
- thestage/services/clients/thestage_api/dtos/task_controller/task_view_response.py +12 -12
- thestage/services/clients/thestage_api/dtos/user_controller/user_profile.py +12 -12
- thestage/services/clients/thestage_api/dtos/validate_token_response.py +11 -11
- thestage/services/config_provider/__init__.py +0 -0
- thestage/services/config_provider/config_provider.py +237 -237
- thestage/services/connect/connect_service.py +193 -196
- thestage/services/connect/dto/remote_server_config.py +9 -9
- thestage/services/container/__init__.py +0 -0
- thestage/services/container/container_service.py +374 -374
- thestage/services/container/mapper/__init__.py +0 -0
- thestage/services/container/mapper/container_mapper.py +30 -30
- thestage/services/core_files/config_entity.py +26 -26
- thestage/services/filesystem_service.py +133 -133
- thestage/services/instance/__init__.py +0 -0
- thestage/services/instance/instance_service.py +303 -303
- thestage/services/instance/mapper/__init__.py +0 -0
- thestage/services/instance/mapper/instance_mapper.py +24 -24
- thestage/services/instance/mapper/selfhosted_mapper.py +33 -33
- thestage/services/logging/byte_print_style.py +5 -5
- thestage/services/logging/dto/log_message.py +15 -15
- thestage/services/logging/dto/log_type.py +6 -6
- thestage/services/logging/exception/log_polling_exception.py +6 -6
- thestage/services/logging/logging_constants.py +3 -3
- thestage/services/logging/logging_service.py +367 -367
- thestage/services/project/__init__.py +0 -0
- thestage/services/project/dto/inference_simulator_dto.py +22 -22
- thestage/services/project/dto/inference_simulator_model_dto.py +20 -20
- thestage/services/project/dto/project_config.py +14 -14
- thestage/services/project/mapper/__init__.py +0 -0
- thestage/services/project/mapper/project_inference_simulator_mapper.py +21 -21
- thestage/services/project/mapper/project_inference_simulator_model_mapper.py +21 -21
- thestage/services/project/mapper/project_task_mapper.py +22 -22
- thestage/services/project/project_service.py +1253 -1253
- thestage/services/remote_server_service.py +609 -609
- thestage/services/service_factory.py +97 -97
- thestage/services/task/dto/task_dto.py +40 -40
- thestage/services/validation_service.py +61 -61
- {thestage-0.6.2.dist-info → thestage-0.6.3.dist-info}/LICENSE.txt +12 -12
- {thestage-0.6.2.dist-info → thestage-0.6.3.dist-info}/METADATA +3 -2
- thestage-0.6.3.dist-info/RECORD +176 -0
- {thestage-0.6.2.dist-info → thestage-0.6.3.dist-info}/WHEEL +1 -1
- thestage-0.6.2.dist-info/RECORD +0 -176
- {thestage-0.6.2.dist-info → thestage-0.6.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,609 +1,609 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import stat
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from time import sleep
|
|
5
|
-
from typing import Optional, List, Tuple
|
|
6
|
-
|
|
7
|
-
import math
|
|
8
|
-
import paramiko
|
|
9
|
-
import typer
|
|
10
|
-
from click import Abort
|
|
11
|
-
from paramiko.client import SSHClient
|
|
12
|
-
from paramiko.pkey import PKey
|
|
13
|
-
from paramiko.sftp_client import SFTPClient
|
|
14
|
-
|
|
15
|
-
from thestage.entities.file_item import FileItemEntity
|
|
16
|
-
from thestage.services.core_files.config_entity import ConfigEntity
|
|
17
|
-
from thestage.exceptions.remote_server_exception import RemoteServerException
|
|
18
|
-
from thestage.helpers.logger.app_logger import app_logger
|
|
19
|
-
from thestage.entities.enums.shell_type import ShellType
|
|
20
|
-
from thestage.helpers.ssh_util import parse_private_key
|
|
21
|
-
from thestage.i18n.translation import __
|
|
22
|
-
from thestage.services.clients.thestage_api.dtos.sftp_path_helper import SftpFileItemEntity
|
|
23
|
-
from thestage.services.config_provider.config_provider import ConfigProvider
|
|
24
|
-
from thestage.services.filesystem_service import FileSystemService
|
|
25
|
-
|
|
26
|
-
old_value: int = 0
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class RemoteServerService:
|
|
30
|
-
__config_provider: ConfigProvider = None
|
|
31
|
-
__file_system_service: FileSystemService = None
|
|
32
|
-
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
file_system_service: FileSystemService,
|
|
36
|
-
config_provider: ConfigProvider,
|
|
37
|
-
):
|
|
38
|
-
self.__file_system_service = file_system_service
|
|
39
|
-
self.__config_provider = config_provider
|
|
40
|
-
|
|
41
|
-
def __get_client(
|
|
42
|
-
self,
|
|
43
|
-
ip_address: str,
|
|
44
|
-
username: str,
|
|
45
|
-
private_key_path: Optional[str],
|
|
46
|
-
) -> Optional[SSHClient]:
|
|
47
|
-
pkey: Optional[PKey] = None
|
|
48
|
-
if private_key_path:
|
|
49
|
-
pkey = parse_private_key(private_key_path)
|
|
50
|
-
if pkey is None:
|
|
51
|
-
typer.echo("Could not identify provided private key (expected RSA, ECDSA, ED25519)")
|
|
52
|
-
raise typer.Exit(1)
|
|
53
|
-
|
|
54
|
-
client = SSHClient()
|
|
55
|
-
try:
|
|
56
|
-
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
57
|
-
client.connect(
|
|
58
|
-
hostname=ip_address,
|
|
59
|
-
username=username,
|
|
60
|
-
timeout=6,
|
|
61
|
-
# key_filename=private_key_path, # key_filename is buggy for certain algorithms/systems apparently
|
|
62
|
-
pkey=pkey,
|
|
63
|
-
allow_agent=private_key_path is None,
|
|
64
|
-
look_for_keys=private_key_path is None,
|
|
65
|
-
)
|
|
66
|
-
return client
|
|
67
|
-
|
|
68
|
-
except Exception as ex:
|
|
69
|
-
if client:
|
|
70
|
-
client.close()
|
|
71
|
-
typer.echo(f"Error connecting to {ip_address} as {username} ({ex})")
|
|
72
|
-
app_logger.error(f"Error connecting to {ip_address} as {username} ({ex})")
|
|
73
|
-
raise RemoteServerException(
|
|
74
|
-
message=__("Unable to connect to remote server"),
|
|
75
|
-
ip_address=ip_address,
|
|
76
|
-
username=username,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def get_shell_from_container(
|
|
81
|
-
self,
|
|
82
|
-
ip_address: str,
|
|
83
|
-
username: str,
|
|
84
|
-
container_name: str,
|
|
85
|
-
private_key_path: Optional[str],
|
|
86
|
-
) -> Optional[ShellType]:
|
|
87
|
-
client: Optional[SSHClient] = self.__get_client(
|
|
88
|
-
ip_address=ip_address,
|
|
89
|
-
username=username,
|
|
90
|
-
private_key_path=private_key_path,
|
|
91
|
-
)
|
|
92
|
-
stdin, stdout, stderr = client.exec_command(f'docker exec -it {container_name} cat /etc/shells', get_pty=True)
|
|
93
|
-
shell: Optional[ShellType] = None
|
|
94
|
-
stdout_lines: List[str] = []
|
|
95
|
-
|
|
96
|
-
for line in stdout.readlines():
|
|
97
|
-
stdout_lines.append(line.rstrip())
|
|
98
|
-
if 'bin/bash' in line:
|
|
99
|
-
shell = ShellType.BASH
|
|
100
|
-
break
|
|
101
|
-
if 'bin/sh' in line:
|
|
102
|
-
shell = ShellType.SH
|
|
103
|
-
break
|
|
104
|
-
client.close()
|
|
105
|
-
|
|
106
|
-
if shell is None:
|
|
107
|
-
for line_item in stdout_lines:
|
|
108
|
-
typer.echo(line_item)
|
|
109
|
-
|
|
110
|
-
return shell
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def connect_to_instance(
|
|
114
|
-
self,
|
|
115
|
-
ip_address: str,
|
|
116
|
-
username: str,
|
|
117
|
-
private_key_path: Optional[str],
|
|
118
|
-
):
|
|
119
|
-
try:
|
|
120
|
-
if private_key_path:
|
|
121
|
-
os.system(f"ssh -o PreferredAuthentications=publickey -o 'IdentitiesOnly=yes' -i {private_key_path} {username}@{ip_address}")
|
|
122
|
-
else:
|
|
123
|
-
os.system(f"ssh -o PreferredAuthentications=publickey {username}@{ip_address}")
|
|
124
|
-
except Abort as e1:
|
|
125
|
-
return
|
|
126
|
-
except Exception as ex:
|
|
127
|
-
app_logger.error(f"Error connecting to {ip_address} as {username} ({ex})")
|
|
128
|
-
raise RemoteServerException(
|
|
129
|
-
message=__("Unable to connect to remote server"),
|
|
130
|
-
ip_address=ip_address,
|
|
131
|
-
username=username,
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def connect_to_container(
|
|
136
|
-
self,
|
|
137
|
-
ip_address: str,
|
|
138
|
-
username: str,
|
|
139
|
-
docker_name: str,
|
|
140
|
-
starting_directory: str,
|
|
141
|
-
shell: ShellType,
|
|
142
|
-
private_key_path: Optional[str],
|
|
143
|
-
):
|
|
144
|
-
try:
|
|
145
|
-
if private_key_path:
|
|
146
|
-
os.system(f"ssh -tt -o PreferredAuthentications=publickey -o 'IdentitiesOnly=yes' -i {private_key_path} {username}@{ip_address} 'docker exec -it {docker_name} sh -c \"cd {starting_directory} && {shell.value}\"'")
|
|
147
|
-
else:
|
|
148
|
-
os.system(f"ssh -tt {username}@{ip_address} 'docker exec -it {docker_name} sh -c \"cd {starting_directory} && {shell.value}\"'")
|
|
149
|
-
except Exception as ex:
|
|
150
|
-
app_logger.exception(f"Error connecting to {ip_address} as {username} ({ex})")
|
|
151
|
-
raise RemoteServerException(
|
|
152
|
-
message=__("Unable to connect to remote server"),
|
|
153
|
-
ip_address=ip_address,
|
|
154
|
-
username=username,
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def __upload_one_file(
|
|
159
|
-
self,
|
|
160
|
-
sftp: SFTPClient,
|
|
161
|
-
src_path: str,
|
|
162
|
-
dest_path: str,
|
|
163
|
-
file_name: str,
|
|
164
|
-
container_path: str,
|
|
165
|
-
file_size: [int] = 100,
|
|
166
|
-
) -> bool:
|
|
167
|
-
has_error = False
|
|
168
|
-
try:
|
|
169
|
-
with typer.progressbar(length=file_size, label=__("Uploading %file_name% (%file_size%)", {'file_name': file_name, 'file_size': self.__convert_size(file_size)})) as progress:
|
|
170
|
-
def __show_result_copy(size: int, full_size: int):
|
|
171
|
-
global old_value
|
|
172
|
-
progress.update(size - (old_value or 0))
|
|
173
|
-
old_value = size
|
|
174
|
-
if old_value == full_size:
|
|
175
|
-
old_value = 0
|
|
176
|
-
sftp.put(localpath=src_path, remotepath=f"{dest_path}", callback=__show_result_copy)
|
|
177
|
-
typer.echo(__('Uploaded to container as %file_path%', {'file_path': container_path}))
|
|
178
|
-
except FileNotFoundError as err:
|
|
179
|
-
app_logger.exception(f"Error uploading file {file_name} to container (file not found): {err}")
|
|
180
|
-
typer.echo(__("Error uploading file: file not found on server"))
|
|
181
|
-
has_error = True
|
|
182
|
-
except Exception as err2:
|
|
183
|
-
typer.echo(err2)
|
|
184
|
-
app_logger.exception(f"Error uploading file {file_name} to container: {err2}")
|
|
185
|
-
typer.echo(__("Error uploading file: undefined server error"))
|
|
186
|
-
has_error = True
|
|
187
|
-
|
|
188
|
-
return has_error
|
|
189
|
-
|
|
190
|
-
def __make_dirs_by_sftp(
|
|
191
|
-
self,
|
|
192
|
-
sftp: SFTPClient,
|
|
193
|
-
path: str,
|
|
194
|
-
):
|
|
195
|
-
full_path = ''
|
|
196
|
-
for item in path.split('/'):
|
|
197
|
-
if item == '':
|
|
198
|
-
continue
|
|
199
|
-
try:
|
|
200
|
-
full_path += f'/{item}'
|
|
201
|
-
sftp.chdir(full_path) # Test if remote_path exists
|
|
202
|
-
except IOError:
|
|
203
|
-
sftp.mkdir(full_path) # Create remote_path
|
|
204
|
-
sftp.chdir(full_path)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def __upload_list_files(
|
|
208
|
-
self,
|
|
209
|
-
sftp: SFTPClient,
|
|
210
|
-
src_item: SftpFileItemEntity,
|
|
211
|
-
):
|
|
212
|
-
if src_item.is_file:
|
|
213
|
-
|
|
214
|
-
get_parent_path = '/'.join(src_item.instance_path.split('/')[0:-1])
|
|
215
|
-
self.__make_dirs_by_sftp(sftp=sftp, path=get_parent_path)
|
|
216
|
-
|
|
217
|
-
self.__upload_one_file(
|
|
218
|
-
sftp=sftp,
|
|
219
|
-
src_path=src_item.path,
|
|
220
|
-
dest_path=src_item.instance_path,
|
|
221
|
-
file_name=src_item.name,
|
|
222
|
-
file_size=src_item.file_size,
|
|
223
|
-
container_path=src_item.container_path
|
|
224
|
-
)
|
|
225
|
-
elif src_item.is_folder:
|
|
226
|
-
self.__make_dirs_by_sftp(sftp=sftp, path=src_item.instance_path)
|
|
227
|
-
for item in src_item.children:
|
|
228
|
-
self.__upload_list_files(
|
|
229
|
-
sftp=sftp,
|
|
230
|
-
src_item=item,
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def __convert_size(self, size_bytes):
|
|
235
|
-
if size_bytes == 0:
|
|
236
|
-
return "0B"
|
|
237
|
-
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
|
238
|
-
i = int(math.floor(math.log(size_bytes, 1024)))
|
|
239
|
-
p = math.pow(1024, i)
|
|
240
|
-
s = round(size_bytes / p, 2)
|
|
241
|
-
return "%s %s" % (s, size_name[i])
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def __download_one_file(
|
|
245
|
-
self,
|
|
246
|
-
sftp: SFTPClient,
|
|
247
|
-
src_path: str,
|
|
248
|
-
dest_path: str,
|
|
249
|
-
file_name: str,
|
|
250
|
-
file_size: [int] = 100
|
|
251
|
-
) -> bool:
|
|
252
|
-
has_error = False
|
|
253
|
-
try:
|
|
254
|
-
with typer.progressbar(length=file_size, label=__("Downloading %file_name% (%file_size%)", {'file_name': file_name, 'file_size': self.__convert_size(file_size)})) as progress:
|
|
255
|
-
def __show_result_copy(size: int, full_size: int):
|
|
256
|
-
global old_value
|
|
257
|
-
progress.update(size - (old_value or 0))
|
|
258
|
-
old_value = size
|
|
259
|
-
if old_value == full_size:
|
|
260
|
-
old_value = 0
|
|
261
|
-
sftp.get(remotepath=src_path, localpath=f"{dest_path}", callback=__show_result_copy)
|
|
262
|
-
typer.echo(__('Downloaded as %file_path%', {'file_path': dest_path}))
|
|
263
|
-
except FileNotFoundError as err:
|
|
264
|
-
app_logger.exception(f"Error retrieving file {file_name} from container (file not found): {err}")
|
|
265
|
-
typer.echo(__("Error retrieving file: file not found on server"))
|
|
266
|
-
has_error = True
|
|
267
|
-
except Exception as err2:
|
|
268
|
-
typer.echo(err2)
|
|
269
|
-
app_logger.exception(f"Error retrieving file {file_name} from container: {err2}")
|
|
270
|
-
typer.echo(__("Error retrieving file: undefined server error"))
|
|
271
|
-
has_error = True
|
|
272
|
-
return has_error
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def __download_list_files(
|
|
276
|
-
self,
|
|
277
|
-
sftp: SFTPClient,
|
|
278
|
-
src_item: SftpFileItemEntity,
|
|
279
|
-
):
|
|
280
|
-
if src_item.is_file:
|
|
281
|
-
self.__file_system_service.get_path('/'.join(src_item.dest_path.split('/')[0:-1]), auto_create=True)
|
|
282
|
-
self.__download_one_file(
|
|
283
|
-
sftp=sftp,
|
|
284
|
-
src_path=src_item.path,
|
|
285
|
-
dest_path=src_item.dest_path,
|
|
286
|
-
file_name=src_item.name,
|
|
287
|
-
file_size=src_item.file_size,
|
|
288
|
-
)
|
|
289
|
-
elif src_item.is_folder:
|
|
290
|
-
self.__file_system_service.get_path(str(Path(src_item.dest_path)), auto_create=True)
|
|
291
|
-
for item in src_item.children:
|
|
292
|
-
self.__download_list_files(
|
|
293
|
-
sftp=sftp,
|
|
294
|
-
src_item=item,
|
|
295
|
-
#dest_path=src_item.dest_path,
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
@staticmethod
|
|
299
|
-
def find_sftp_server_path(
|
|
300
|
-
client: SSHClient,
|
|
301
|
-
) -> Optional[str]:
|
|
302
|
-
stdin, stdout, stderr = client.exec_command(f'whereis sftp-server', get_pty=True)
|
|
303
|
-
for line in stdout.readlines():
|
|
304
|
-
pre_line = line.replace('sftp-server:', '')
|
|
305
|
-
for command in pre_line.strip().split(' '):
|
|
306
|
-
tmp = command.strip()
|
|
307
|
-
if tmp:
|
|
308
|
-
if tmp.endswith('/sftp-server'):
|
|
309
|
-
return tmp
|
|
310
|
-
return None
|
|
311
|
-
|
|
312
|
-
def copy_data_on_container(
|
|
313
|
-
self,
|
|
314
|
-
client: SSHClient,
|
|
315
|
-
docker_name: str,
|
|
316
|
-
src_path: str,
|
|
317
|
-
dest_path: str,
|
|
318
|
-
is_recursive: bool = False,
|
|
319
|
-
):
|
|
320
|
-
self.start_command_on_container(
|
|
321
|
-
client=client,
|
|
322
|
-
docker_name=docker_name,
|
|
323
|
-
command=['cp ' + ('-R' if is_recursive else '') + f' {src_path}' + f' {dest_path}'],
|
|
324
|
-
)
|
|
325
|
-
# TODO: dont now how, need check for copy end!!!!
|
|
326
|
-
sleep(3)
|
|
327
|
-
|
|
328
|
-
@staticmethod
|
|
329
|
-
def start_command_on_container(
|
|
330
|
-
client: SSHClient,
|
|
331
|
-
docker_name: str,
|
|
332
|
-
command: List[str],
|
|
333
|
-
is_bash: bool = False,
|
|
334
|
-
):
|
|
335
|
-
if is_bash:
|
|
336
|
-
stdin, stdout, stderr = client.exec_command(f'docker exec -it {docker_name} /bin/bash -c "{";".join(command)}"', get_pty=True)
|
|
337
|
-
else:
|
|
338
|
-
stdin, stdout, stderr = client.exec_command(f'docker exec -it {docker_name} {command[0]}', get_pty=True)
|
|
339
|
-
for line in stdout.readlines():
|
|
340
|
-
pass
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
def __build_sftp_client(
|
|
344
|
-
self,
|
|
345
|
-
ip_address: str,
|
|
346
|
-
username: str,
|
|
347
|
-
private_key_path: Optional[str],
|
|
348
|
-
) -> Tuple[SSHClient, SFTPClient]:
|
|
349
|
-
client: Optional[SSHClient] = self.__get_client(ip_address=ip_address, username=username, private_key_path=private_key_path)
|
|
350
|
-
sftp_server_path = self.find_sftp_server_path(client=client)
|
|
351
|
-
|
|
352
|
-
if not sftp_server_path:
|
|
353
|
-
typer.echo(__('SFTP server is not installed on the server instance'))
|
|
354
|
-
raise typer.Exit(1)
|
|
355
|
-
|
|
356
|
-
chan = client.get_transport().open_session()
|
|
357
|
-
# chan.exec_command("sudo su -c /usr/lib/openssh/sftp-server")
|
|
358
|
-
chan.exec_command(f"sudo su -c {sftp_server_path}")
|
|
359
|
-
sftp = paramiko.SFTPClient(chan)
|
|
360
|
-
|
|
361
|
-
return client, sftp
|
|
362
|
-
|
|
363
|
-
# TODO what the fuck does this method do?
|
|
364
|
-
@staticmethod
|
|
365
|
-
def _check_if_file_name_in_path(path: str, file_template: Optional[str] = None) -> bool:
|
|
366
|
-
# strange logic
|
|
367
|
-
file_name = path.split('/')[-1] if path else None
|
|
368
|
-
if file_name and '.' in file_name:
|
|
369
|
-
if file_template and '.' in file_template:
|
|
370
|
-
extension = file_template.split('.')[-1]
|
|
371
|
-
if extension in file_name:
|
|
372
|
-
return True
|
|
373
|
-
else:
|
|
374
|
-
return True
|
|
375
|
-
return False
|
|
376
|
-
|
|
377
|
-
@staticmethod
|
|
378
|
-
def _get_parent_from_path(path: str) -> str:
|
|
379
|
-
pre_path = '/'.join(path.split('/')[0:-1])
|
|
380
|
-
if not pre_path:
|
|
381
|
-
return '/'
|
|
382
|
-
else:
|
|
383
|
-
return pre_path
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
def __build_path_mapping_for_upload(
|
|
387
|
-
self,
|
|
388
|
-
files: List[FileItemEntity],
|
|
389
|
-
instance_path: str,
|
|
390
|
-
container_path: str,
|
|
391
|
-
copy_only_folder_contents: bool,
|
|
392
|
-
has_parent: bool = False,
|
|
393
|
-
) -> List[SftpFileItemEntity]:
|
|
394
|
-
result = []
|
|
395
|
-
for item in files:
|
|
396
|
-
elem = SftpFileItemEntity.model_validate(item.model_dump())
|
|
397
|
-
if item.is_file:
|
|
398
|
-
# TODO god knows what is happening here
|
|
399
|
-
# if uploading a single file without extension: has_file_name must be true if destination is not a folder
|
|
400
|
-
has_file_name = self._check_if_file_name_in_path(
|
|
401
|
-
path=instance_path,
|
|
402
|
-
file_template=item.name,
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
if not has_parent and has_file_name:
|
|
406
|
-
elem.instance_path = instance_path
|
|
407
|
-
elem.container_path = container_path
|
|
408
|
-
else:
|
|
409
|
-
elem.instance_path = f"{instance_path}/{item.name}"
|
|
410
|
-
elem.container_path = f"{container_path}/{item.name}"
|
|
411
|
-
|
|
412
|
-
elem.dest_path = elem.instance_path
|
|
413
|
-
|
|
414
|
-
else:
|
|
415
|
-
if not has_parent and copy_only_folder_contents:
|
|
416
|
-
elem.instance_path = instance_path
|
|
417
|
-
elem.container_path = container_path
|
|
418
|
-
else:
|
|
419
|
-
elem.instance_path = f"{instance_path}/{item.name}"
|
|
420
|
-
elem.container_path = f"{container_path}/{item.name}"
|
|
421
|
-
|
|
422
|
-
elem.dest_path = elem.instance_path
|
|
423
|
-
|
|
424
|
-
if len(item.children) > 0:
|
|
425
|
-
elem.children = []
|
|
426
|
-
elem.children.extend(self.__build_path_mapping_for_upload(
|
|
427
|
-
files=item.children,
|
|
428
|
-
instance_path=elem.instance_path,
|
|
429
|
-
container_path=elem.container_path,
|
|
430
|
-
copy_only_folder_contents=copy_only_folder_contents,
|
|
431
|
-
has_parent=True,
|
|
432
|
-
))
|
|
433
|
-
|
|
434
|
-
result.append(elem)
|
|
435
|
-
|
|
436
|
-
return result
|
|
437
|
-
|
|
438
|
-
def upload_data_to_container(
|
|
439
|
-
self,
|
|
440
|
-
ip_address: str,
|
|
441
|
-
username: str,
|
|
442
|
-
src_path: str,
|
|
443
|
-
dest_path: str,
|
|
444
|
-
instance_path: str,
|
|
445
|
-
container_path: str,
|
|
446
|
-
copy_only_folder_contents: bool,
|
|
447
|
-
private_key_path: Optional[str],
|
|
448
|
-
):
|
|
449
|
-
has_error = False
|
|
450
|
-
client, sftp = self.__build_sftp_client(username=username, ip_address=ip_address, private_key_path=private_key_path)
|
|
451
|
-
|
|
452
|
-
origin_files: List[FileItemEntity] = self.__file_system_service.get_path_items(src_path)
|
|
453
|
-
|
|
454
|
-
files: List[SftpFileItemEntity] = self.__build_path_mapping_for_upload(
|
|
455
|
-
files=origin_files,
|
|
456
|
-
instance_path=instance_path,
|
|
457
|
-
container_path=container_path,
|
|
458
|
-
copy_only_folder_contents=copy_only_folder_contents
|
|
459
|
-
)
|
|
460
|
-
|
|
461
|
-
try:
|
|
462
|
-
for item in files:
|
|
463
|
-
self.__upload_list_files(
|
|
464
|
-
sftp=sftp,
|
|
465
|
-
src_item=item,
|
|
466
|
-
)
|
|
467
|
-
|
|
468
|
-
if len(files) == 0:
|
|
469
|
-
typer.echo(__("No source files could be found on the server"))
|
|
470
|
-
raise typer.Exit(1)
|
|
471
|
-
|
|
472
|
-
if len(files[0].children) == 0 and files[0].is_folder:
|
|
473
|
-
typer.echo(__("Source directory is empty"))
|
|
474
|
-
raise typer.Exit(1)
|
|
475
|
-
|
|
476
|
-
except FileNotFoundError as err:
|
|
477
|
-
app_logger.error(f"Error uploading file to container {ip_address}, user {username} (file not found): {err}")
|
|
478
|
-
typer.echo(__("Error uploading file: file not found on server"))
|
|
479
|
-
has_error = True
|
|
480
|
-
finally:
|
|
481
|
-
sftp.close()
|
|
482
|
-
|
|
483
|
-
client.close()
|
|
484
|
-
if has_error:
|
|
485
|
-
raise typer.Exit(1)
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
def __read_remote_path_items_for_download(
|
|
489
|
-
self,
|
|
490
|
-
sftp: SFTPClient,
|
|
491
|
-
current_path: str,
|
|
492
|
-
dest_path: str,
|
|
493
|
-
instance_path: str,
|
|
494
|
-
copy_only_folder_contents: bool,
|
|
495
|
-
depth: int = 0,
|
|
496
|
-
) -> List[SftpFileItemEntity]:
|
|
497
|
-
config = self.__config_provider.get_config()
|
|
498
|
-
path_items = []
|
|
499
|
-
try:
|
|
500
|
-
root_stat = sftp.stat(current_path)
|
|
501
|
-
parent = SftpFileItemEntity(
|
|
502
|
-
name=current_path.split('/')[-1],
|
|
503
|
-
path=current_path,
|
|
504
|
-
is_file=stat.S_ISREG(root_stat.st_mode),
|
|
505
|
-
is_folder=stat.S_ISDIR(root_stat.st_mode),
|
|
506
|
-
file_size=root_stat.st_size,
|
|
507
|
-
instance_path=instance_path,
|
|
508
|
-
dest_path=dest_path,
|
|
509
|
-
)
|
|
510
|
-
|
|
511
|
-
if depth == 0 and not copy_only_folder_contents and parent.is_folder:
|
|
512
|
-
parent.dest_path = parent.dest_path.rstrip("/") + "/" + parent.name
|
|
513
|
-
path_items.append(parent)
|
|
514
|
-
if parent.is_file:
|
|
515
|
-
# must be true if destination is file
|
|
516
|
-
# must be false if destination is dir
|
|
517
|
-
# if destination does not exist, destination is file (true)
|
|
518
|
-
treat_as_file = True
|
|
519
|
-
dest_fullpath = Path(config.runtime.working_directory + '/' + dest_path)
|
|
520
|
-
if dest_fullpath.exists() and dest_fullpath.is_dir():
|
|
521
|
-
treat_as_file = False
|
|
522
|
-
|
|
523
|
-
if not treat_as_file:
|
|
524
|
-
parent.dest_path += f"{parent.name}" if parent.dest_path.endswith('/') else f"/{parent.name}"
|
|
525
|
-
|
|
526
|
-
elif parent.is_folder:
|
|
527
|
-
sftp.chdir(current_path)
|
|
528
|
-
if depth > 0:
|
|
529
|
-
parent.dest_path += f"{parent.name}" if parent.dest_path.endswith('/') else f"/{parent.name}"
|
|
530
|
-
for item in sftp.listdir_attr():
|
|
531
|
-
next_path = f'{current_path}/{item.filename}'
|
|
532
|
-
is_dir = stat.S_ISDIR(item.st_mode)
|
|
533
|
-
is_file = stat.S_ISREG(item.st_mode)
|
|
534
|
-
if is_file:
|
|
535
|
-
parent.children.append(SftpFileItemEntity(
|
|
536
|
-
name=item.filename,
|
|
537
|
-
path=next_path,
|
|
538
|
-
is_file=is_file,
|
|
539
|
-
is_folder=is_dir,
|
|
540
|
-
file_size=item.st_size,
|
|
541
|
-
instance_path=f'{instance_path}/{item.filename}',
|
|
542
|
-
dest_path=f'{parent.dest_path}/{item.filename}',
|
|
543
|
-
))
|
|
544
|
-
elif is_dir:
|
|
545
|
-
parent.children.extend(self.__read_remote_path_items_for_download(
|
|
546
|
-
sftp=sftp,
|
|
547
|
-
current_path=next_path,
|
|
548
|
-
dest_path=parent.dest_path,
|
|
549
|
-
instance_path=parent.instance_path,
|
|
550
|
-
depth=depth + 1,
|
|
551
|
-
copy_only_folder_contents=copy_only_folder_contents,
|
|
552
|
-
))
|
|
553
|
-
return path_items
|
|
554
|
-
except FileNotFoundError as ex:
|
|
555
|
-
app_logger.exception(f"Unable to read remote file list: {ex}")
|
|
556
|
-
typer.echo(__("Could not find the requested object on remote instance: %path%", {'path': current_path}))
|
|
557
|
-
raise typer.Exit(1)
|
|
558
|
-
except Exception as ex:
|
|
559
|
-
app_logger.exception(f"Error occurred: {ex}")
|
|
560
|
-
typer.echo(__('Error occurred while processing the file'))
|
|
561
|
-
raise typer.Exit(1)
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
def download_data_from_container(
|
|
565
|
-
self,
|
|
566
|
-
ip_address: str,
|
|
567
|
-
username: str,
|
|
568
|
-
dest_path: str,
|
|
569
|
-
instance_path: str,
|
|
570
|
-
copy_only_folder_contents: bool,
|
|
571
|
-
private_key_path: Optional[str],
|
|
572
|
-
):
|
|
573
|
-
has_error = False
|
|
574
|
-
|
|
575
|
-
client, sftp = self.__build_sftp_client(username=username, ip_address=ip_address, private_key_path=private_key_path)
|
|
576
|
-
|
|
577
|
-
try:
|
|
578
|
-
files: List[SftpFileItemEntity] = self.__read_remote_path_items_for_download(
|
|
579
|
-
sftp=sftp,
|
|
580
|
-
current_path=instance_path,
|
|
581
|
-
instance_path=instance_path,
|
|
582
|
-
dest_path=dest_path,
|
|
583
|
-
copy_only_folder_contents=copy_only_folder_contents,
|
|
584
|
-
)
|
|
585
|
-
|
|
586
|
-
if len(files) == 0:
|
|
587
|
-
typer.echo(__("No source files could be found on the server"))
|
|
588
|
-
raise typer.Exit(1)
|
|
589
|
-
|
|
590
|
-
if len(files[0].children) == 0 and files[0].is_folder:
|
|
591
|
-
typer.echo(__("Source directory is empty"))
|
|
592
|
-
raise typer.Exit(1)
|
|
593
|
-
|
|
594
|
-
for item in files:
|
|
595
|
-
self.__download_list_files(
|
|
596
|
-
sftp=sftp,
|
|
597
|
-
src_item=item,
|
|
598
|
-
)
|
|
599
|
-
except FileNotFoundError as err:
|
|
600
|
-
print(err)
|
|
601
|
-
app_logger.error(f"Error uploading file to container {ip_address} for user {username} (file not found): {err}")
|
|
602
|
-
typer.echo(__("Error uploading file: file not found on server"))
|
|
603
|
-
has_error = True
|
|
604
|
-
finally:
|
|
605
|
-
sftp.close()
|
|
606
|
-
|
|
607
|
-
client.close()
|
|
608
|
-
if has_error:
|
|
609
|
-
raise typer.Exit(1)
|
|
1
|
+
import os
|
|
2
|
+
import stat
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from time import sleep
|
|
5
|
+
from typing import Optional, List, Tuple
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
import paramiko
|
|
9
|
+
import typer
|
|
10
|
+
from click import Abort
|
|
11
|
+
from paramiko.client import SSHClient
|
|
12
|
+
from paramiko.pkey import PKey
|
|
13
|
+
from paramiko.sftp_client import SFTPClient
|
|
14
|
+
|
|
15
|
+
from thestage.entities.file_item import FileItemEntity
|
|
16
|
+
from thestage.services.core_files.config_entity import ConfigEntity
|
|
17
|
+
from thestage.exceptions.remote_server_exception import RemoteServerException
|
|
18
|
+
from thestage.helpers.logger.app_logger import app_logger
|
|
19
|
+
from thestage.entities.enums.shell_type import ShellType
|
|
20
|
+
from thestage.helpers.ssh_util import parse_private_key
|
|
21
|
+
from thestage.i18n.translation import __
|
|
22
|
+
from thestage.services.clients.thestage_api.dtos.sftp_path_helper import SftpFileItemEntity
|
|
23
|
+
from thestage.services.config_provider.config_provider import ConfigProvider
|
|
24
|
+
from thestage.services.filesystem_service import FileSystemService
|
|
25
|
+
|
|
26
|
+
old_value: int = 0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RemoteServerService:
|
|
30
|
+
__config_provider: ConfigProvider = None
|
|
31
|
+
__file_system_service: FileSystemService = None
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
file_system_service: FileSystemService,
|
|
36
|
+
config_provider: ConfigProvider,
|
|
37
|
+
):
|
|
38
|
+
self.__file_system_service = file_system_service
|
|
39
|
+
self.__config_provider = config_provider
|
|
40
|
+
|
|
41
|
+
def __get_client(
|
|
42
|
+
self,
|
|
43
|
+
ip_address: str,
|
|
44
|
+
username: str,
|
|
45
|
+
private_key_path: Optional[str],
|
|
46
|
+
) -> Optional[SSHClient]:
|
|
47
|
+
pkey: Optional[PKey] = None
|
|
48
|
+
if private_key_path:
|
|
49
|
+
pkey = parse_private_key(private_key_path)
|
|
50
|
+
if pkey is None:
|
|
51
|
+
typer.echo("Could not identify provided private key (expected RSA, ECDSA, ED25519)")
|
|
52
|
+
raise typer.Exit(1)
|
|
53
|
+
|
|
54
|
+
client = SSHClient()
|
|
55
|
+
try:
|
|
56
|
+
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
57
|
+
client.connect(
|
|
58
|
+
hostname=ip_address,
|
|
59
|
+
username=username,
|
|
60
|
+
timeout=6,
|
|
61
|
+
# key_filename=private_key_path, # key_filename is buggy for certain algorithms/systems apparently
|
|
62
|
+
pkey=pkey,
|
|
63
|
+
allow_agent=private_key_path is None,
|
|
64
|
+
look_for_keys=private_key_path is None,
|
|
65
|
+
)
|
|
66
|
+
return client
|
|
67
|
+
|
|
68
|
+
except Exception as ex:
|
|
69
|
+
if client:
|
|
70
|
+
client.close()
|
|
71
|
+
typer.echo(f"Error connecting to {ip_address} as {username} ({ex})")
|
|
72
|
+
app_logger.error(f"Error connecting to {ip_address} as {username} ({ex})")
|
|
73
|
+
raise RemoteServerException(
|
|
74
|
+
message=__("Unable to connect to remote server"),
|
|
75
|
+
ip_address=ip_address,
|
|
76
|
+
username=username,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_shell_from_container(
|
|
81
|
+
self,
|
|
82
|
+
ip_address: str,
|
|
83
|
+
username: str,
|
|
84
|
+
container_name: str,
|
|
85
|
+
private_key_path: Optional[str],
|
|
86
|
+
) -> Optional[ShellType]:
|
|
87
|
+
client: Optional[SSHClient] = self.__get_client(
|
|
88
|
+
ip_address=ip_address,
|
|
89
|
+
username=username,
|
|
90
|
+
private_key_path=private_key_path,
|
|
91
|
+
)
|
|
92
|
+
stdin, stdout, stderr = client.exec_command(f'docker exec -it {container_name} cat /etc/shells', get_pty=True)
|
|
93
|
+
shell: Optional[ShellType] = None
|
|
94
|
+
stdout_lines: List[str] = []
|
|
95
|
+
|
|
96
|
+
for line in stdout.readlines():
|
|
97
|
+
stdout_lines.append(line.rstrip())
|
|
98
|
+
if 'bin/bash' in line:
|
|
99
|
+
shell = ShellType.BASH
|
|
100
|
+
break
|
|
101
|
+
if 'bin/sh' in line:
|
|
102
|
+
shell = ShellType.SH
|
|
103
|
+
break
|
|
104
|
+
client.close()
|
|
105
|
+
|
|
106
|
+
if shell is None:
|
|
107
|
+
for line_item in stdout_lines:
|
|
108
|
+
typer.echo(line_item)
|
|
109
|
+
|
|
110
|
+
return shell
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def connect_to_instance(
|
|
114
|
+
self,
|
|
115
|
+
ip_address: str,
|
|
116
|
+
username: str,
|
|
117
|
+
private_key_path: Optional[str],
|
|
118
|
+
):
|
|
119
|
+
try:
|
|
120
|
+
if private_key_path:
|
|
121
|
+
os.system(f"ssh -o PreferredAuthentications=publickey -o 'IdentitiesOnly=yes' -i {private_key_path} {username}@{ip_address}")
|
|
122
|
+
else:
|
|
123
|
+
os.system(f"ssh -o PreferredAuthentications=publickey {username}@{ip_address}")
|
|
124
|
+
except Abort as e1:
|
|
125
|
+
return
|
|
126
|
+
except Exception as ex:
|
|
127
|
+
app_logger.error(f"Error connecting to {ip_address} as {username} ({ex})")
|
|
128
|
+
raise RemoteServerException(
|
|
129
|
+
message=__("Unable to connect to remote server"),
|
|
130
|
+
ip_address=ip_address,
|
|
131
|
+
username=username,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def connect_to_container(
|
|
136
|
+
self,
|
|
137
|
+
ip_address: str,
|
|
138
|
+
username: str,
|
|
139
|
+
docker_name: str,
|
|
140
|
+
starting_directory: str,
|
|
141
|
+
shell: ShellType,
|
|
142
|
+
private_key_path: Optional[str],
|
|
143
|
+
):
|
|
144
|
+
try:
|
|
145
|
+
if private_key_path:
|
|
146
|
+
os.system(f"ssh -tt -o PreferredAuthentications=publickey -o 'IdentitiesOnly=yes' -i {private_key_path} {username}@{ip_address} 'docker exec -it {docker_name} sh -c \"cd {starting_directory} && {shell.value}\"'")
|
|
147
|
+
else:
|
|
148
|
+
os.system(f"ssh -tt {username}@{ip_address} 'docker exec -it {docker_name} sh -c \"cd {starting_directory} && {shell.value}\"'")
|
|
149
|
+
except Exception as ex:
|
|
150
|
+
app_logger.exception(f"Error connecting to {ip_address} as {username} ({ex})")
|
|
151
|
+
raise RemoteServerException(
|
|
152
|
+
message=__("Unable to connect to remote server"),
|
|
153
|
+
ip_address=ip_address,
|
|
154
|
+
username=username,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def __upload_one_file(
|
|
159
|
+
self,
|
|
160
|
+
sftp: SFTPClient,
|
|
161
|
+
src_path: str,
|
|
162
|
+
dest_path: str,
|
|
163
|
+
file_name: str,
|
|
164
|
+
container_path: str,
|
|
165
|
+
file_size: [int] = 100,
|
|
166
|
+
) -> bool:
|
|
167
|
+
has_error = False
|
|
168
|
+
try:
|
|
169
|
+
with typer.progressbar(length=file_size, label=__("Uploading %file_name% (%file_size%)", {'file_name': file_name, 'file_size': self.__convert_size(file_size)})) as progress:
|
|
170
|
+
def __show_result_copy(size: int, full_size: int):
|
|
171
|
+
global old_value
|
|
172
|
+
progress.update(size - (old_value or 0))
|
|
173
|
+
old_value = size
|
|
174
|
+
if old_value == full_size:
|
|
175
|
+
old_value = 0
|
|
176
|
+
sftp.put(localpath=src_path, remotepath=f"{dest_path}", callback=__show_result_copy)
|
|
177
|
+
typer.echo(__('Uploaded to container as %file_path%', {'file_path': container_path}))
|
|
178
|
+
except FileNotFoundError as err:
|
|
179
|
+
app_logger.exception(f"Error uploading file {file_name} to container (file not found): {err}")
|
|
180
|
+
typer.echo(__("Error uploading file: file not found on server"))
|
|
181
|
+
has_error = True
|
|
182
|
+
except Exception as err2:
|
|
183
|
+
typer.echo(err2)
|
|
184
|
+
app_logger.exception(f"Error uploading file {file_name} to container: {err2}")
|
|
185
|
+
typer.echo(__("Error uploading file: undefined server error"))
|
|
186
|
+
has_error = True
|
|
187
|
+
|
|
188
|
+
return has_error
|
|
189
|
+
|
|
190
|
+
def __make_dirs_by_sftp(
|
|
191
|
+
self,
|
|
192
|
+
sftp: SFTPClient,
|
|
193
|
+
path: str,
|
|
194
|
+
):
|
|
195
|
+
full_path = ''
|
|
196
|
+
for item in path.split('/'):
|
|
197
|
+
if item == '':
|
|
198
|
+
continue
|
|
199
|
+
try:
|
|
200
|
+
full_path += f'/{item}'
|
|
201
|
+
sftp.chdir(full_path) # Test if remote_path exists
|
|
202
|
+
except IOError:
|
|
203
|
+
sftp.mkdir(full_path) # Create remote_path
|
|
204
|
+
sftp.chdir(full_path)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def __upload_list_files(
|
|
208
|
+
self,
|
|
209
|
+
sftp: SFTPClient,
|
|
210
|
+
src_item: SftpFileItemEntity,
|
|
211
|
+
):
|
|
212
|
+
if src_item.is_file:
|
|
213
|
+
|
|
214
|
+
get_parent_path = '/'.join(src_item.instance_path.split('/')[0:-1])
|
|
215
|
+
self.__make_dirs_by_sftp(sftp=sftp, path=get_parent_path)
|
|
216
|
+
|
|
217
|
+
self.__upload_one_file(
|
|
218
|
+
sftp=sftp,
|
|
219
|
+
src_path=src_item.path,
|
|
220
|
+
dest_path=src_item.instance_path,
|
|
221
|
+
file_name=src_item.name,
|
|
222
|
+
file_size=src_item.file_size,
|
|
223
|
+
container_path=src_item.container_path
|
|
224
|
+
)
|
|
225
|
+
elif src_item.is_folder:
|
|
226
|
+
self.__make_dirs_by_sftp(sftp=sftp, path=src_item.instance_path)
|
|
227
|
+
for item in src_item.children:
|
|
228
|
+
self.__upload_list_files(
|
|
229
|
+
sftp=sftp,
|
|
230
|
+
src_item=item,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def __convert_size(self, size_bytes):
|
|
235
|
+
if size_bytes == 0:
|
|
236
|
+
return "0B"
|
|
237
|
+
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
|
238
|
+
i = int(math.floor(math.log(size_bytes, 1024)))
|
|
239
|
+
p = math.pow(1024, i)
|
|
240
|
+
s = round(size_bytes / p, 2)
|
|
241
|
+
return "%s %s" % (s, size_name[i])
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def __download_one_file(
|
|
245
|
+
self,
|
|
246
|
+
sftp: SFTPClient,
|
|
247
|
+
src_path: str,
|
|
248
|
+
dest_path: str,
|
|
249
|
+
file_name: str,
|
|
250
|
+
file_size: [int] = 100
|
|
251
|
+
) -> bool:
|
|
252
|
+
has_error = False
|
|
253
|
+
try:
|
|
254
|
+
with typer.progressbar(length=file_size, label=__("Downloading %file_name% (%file_size%)", {'file_name': file_name, 'file_size': self.__convert_size(file_size)})) as progress:
|
|
255
|
+
def __show_result_copy(size: int, full_size: int):
|
|
256
|
+
global old_value
|
|
257
|
+
progress.update(size - (old_value or 0))
|
|
258
|
+
old_value = size
|
|
259
|
+
if old_value == full_size:
|
|
260
|
+
old_value = 0
|
|
261
|
+
sftp.get(remotepath=src_path, localpath=f"{dest_path}", callback=__show_result_copy)
|
|
262
|
+
typer.echo(__('Downloaded as %file_path%', {'file_path': dest_path}))
|
|
263
|
+
except FileNotFoundError as err:
|
|
264
|
+
app_logger.exception(f"Error retrieving file {file_name} from container (file not found): {err}")
|
|
265
|
+
typer.echo(__("Error retrieving file: file not found on server"))
|
|
266
|
+
has_error = True
|
|
267
|
+
except Exception as err2:
|
|
268
|
+
typer.echo(err2)
|
|
269
|
+
app_logger.exception(f"Error retrieving file {file_name} from container: {err2}")
|
|
270
|
+
typer.echo(__("Error retrieving file: undefined server error"))
|
|
271
|
+
has_error = True
|
|
272
|
+
return has_error
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def __download_list_files(
|
|
276
|
+
self,
|
|
277
|
+
sftp: SFTPClient,
|
|
278
|
+
src_item: SftpFileItemEntity,
|
|
279
|
+
):
|
|
280
|
+
if src_item.is_file:
|
|
281
|
+
self.__file_system_service.get_path('/'.join(src_item.dest_path.split('/')[0:-1]), auto_create=True)
|
|
282
|
+
self.__download_one_file(
|
|
283
|
+
sftp=sftp,
|
|
284
|
+
src_path=src_item.path,
|
|
285
|
+
dest_path=src_item.dest_path,
|
|
286
|
+
file_name=src_item.name,
|
|
287
|
+
file_size=src_item.file_size,
|
|
288
|
+
)
|
|
289
|
+
elif src_item.is_folder:
|
|
290
|
+
self.__file_system_service.get_path(str(Path(src_item.dest_path)), auto_create=True)
|
|
291
|
+
for item in src_item.children:
|
|
292
|
+
self.__download_list_files(
|
|
293
|
+
sftp=sftp,
|
|
294
|
+
src_item=item,
|
|
295
|
+
#dest_path=src_item.dest_path,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
@staticmethod
|
|
299
|
+
def find_sftp_server_path(
|
|
300
|
+
client: SSHClient,
|
|
301
|
+
) -> Optional[str]:
|
|
302
|
+
stdin, stdout, stderr = client.exec_command(f'whereis sftp-server', get_pty=True)
|
|
303
|
+
for line in stdout.readlines():
|
|
304
|
+
pre_line = line.replace('sftp-server:', '')
|
|
305
|
+
for command in pre_line.strip().split(' '):
|
|
306
|
+
tmp = command.strip()
|
|
307
|
+
if tmp:
|
|
308
|
+
if tmp.endswith('/sftp-server'):
|
|
309
|
+
return tmp
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
def copy_data_on_container(
|
|
313
|
+
self,
|
|
314
|
+
client: SSHClient,
|
|
315
|
+
docker_name: str,
|
|
316
|
+
src_path: str,
|
|
317
|
+
dest_path: str,
|
|
318
|
+
is_recursive: bool = False,
|
|
319
|
+
):
|
|
320
|
+
self.start_command_on_container(
|
|
321
|
+
client=client,
|
|
322
|
+
docker_name=docker_name,
|
|
323
|
+
command=['cp ' + ('-R' if is_recursive else '') + f' {src_path}' + f' {dest_path}'],
|
|
324
|
+
)
|
|
325
|
+
# TODO: dont now how, need check for copy end!!!!
|
|
326
|
+
sleep(3)
|
|
327
|
+
|
|
328
|
+
@staticmethod
|
|
329
|
+
def start_command_on_container(
|
|
330
|
+
client: SSHClient,
|
|
331
|
+
docker_name: str,
|
|
332
|
+
command: List[str],
|
|
333
|
+
is_bash: bool = False,
|
|
334
|
+
):
|
|
335
|
+
if is_bash:
|
|
336
|
+
stdin, stdout, stderr = client.exec_command(f'docker exec -it {docker_name} /bin/bash -c "{";".join(command)}"', get_pty=True)
|
|
337
|
+
else:
|
|
338
|
+
stdin, stdout, stderr = client.exec_command(f'docker exec -it {docker_name} {command[0]}', get_pty=True)
|
|
339
|
+
for line in stdout.readlines():
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def __build_sftp_client(
|
|
344
|
+
self,
|
|
345
|
+
ip_address: str,
|
|
346
|
+
username: str,
|
|
347
|
+
private_key_path: Optional[str],
|
|
348
|
+
) -> Tuple[SSHClient, SFTPClient]:
|
|
349
|
+
client: Optional[SSHClient] = self.__get_client(ip_address=ip_address, username=username, private_key_path=private_key_path)
|
|
350
|
+
sftp_server_path = self.find_sftp_server_path(client=client)
|
|
351
|
+
|
|
352
|
+
if not sftp_server_path:
|
|
353
|
+
typer.echo(__('SFTP server is not installed on the server instance'))
|
|
354
|
+
raise typer.Exit(1)
|
|
355
|
+
|
|
356
|
+
chan = client.get_transport().open_session()
|
|
357
|
+
# chan.exec_command("sudo su -c /usr/lib/openssh/sftp-server")
|
|
358
|
+
chan.exec_command(f"sudo su -c {sftp_server_path}")
|
|
359
|
+
sftp = paramiko.SFTPClient(chan)
|
|
360
|
+
|
|
361
|
+
return client, sftp
|
|
362
|
+
|
|
363
|
+
# TODO what the fuck does this method do?
|
|
364
|
+
@staticmethod
|
|
365
|
+
def _check_if_file_name_in_path(path: str, file_template: Optional[str] = None) -> bool:
|
|
366
|
+
# strange logic
|
|
367
|
+
file_name = path.split('/')[-1] if path else None
|
|
368
|
+
if file_name and '.' in file_name:
|
|
369
|
+
if file_template and '.' in file_template:
|
|
370
|
+
extension = file_template.split('.')[-1]
|
|
371
|
+
if extension in file_name:
|
|
372
|
+
return True
|
|
373
|
+
else:
|
|
374
|
+
return True
|
|
375
|
+
return False
|
|
376
|
+
|
|
377
|
+
@staticmethod
|
|
378
|
+
def _get_parent_from_path(path: str) -> str:
|
|
379
|
+
pre_path = '/'.join(path.split('/')[0:-1])
|
|
380
|
+
if not pre_path:
|
|
381
|
+
return '/'
|
|
382
|
+
else:
|
|
383
|
+
return pre_path
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def __build_path_mapping_for_upload(
|
|
387
|
+
self,
|
|
388
|
+
files: List[FileItemEntity],
|
|
389
|
+
instance_path: str,
|
|
390
|
+
container_path: str,
|
|
391
|
+
copy_only_folder_contents: bool,
|
|
392
|
+
has_parent: bool = False,
|
|
393
|
+
) -> List[SftpFileItemEntity]:
|
|
394
|
+
result = []
|
|
395
|
+
for item in files:
|
|
396
|
+
elem = SftpFileItemEntity.model_validate(item.model_dump())
|
|
397
|
+
if item.is_file:
|
|
398
|
+
# TODO god knows what is happening here
|
|
399
|
+
# if uploading a single file without extension: has_file_name must be true if destination is not a folder
|
|
400
|
+
has_file_name = self._check_if_file_name_in_path(
|
|
401
|
+
path=instance_path,
|
|
402
|
+
file_template=item.name,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
if not has_parent and has_file_name:
|
|
406
|
+
elem.instance_path = instance_path
|
|
407
|
+
elem.container_path = container_path
|
|
408
|
+
else:
|
|
409
|
+
elem.instance_path = f"{instance_path}/{item.name}"
|
|
410
|
+
elem.container_path = f"{container_path}/{item.name}"
|
|
411
|
+
|
|
412
|
+
elem.dest_path = elem.instance_path
|
|
413
|
+
|
|
414
|
+
else:
|
|
415
|
+
if not has_parent and copy_only_folder_contents:
|
|
416
|
+
elem.instance_path = instance_path
|
|
417
|
+
elem.container_path = container_path
|
|
418
|
+
else:
|
|
419
|
+
elem.instance_path = f"{instance_path}/{item.name}"
|
|
420
|
+
elem.container_path = f"{container_path}/{item.name}"
|
|
421
|
+
|
|
422
|
+
elem.dest_path = elem.instance_path
|
|
423
|
+
|
|
424
|
+
if len(item.children) > 0:
|
|
425
|
+
elem.children = []
|
|
426
|
+
elem.children.extend(self.__build_path_mapping_for_upload(
|
|
427
|
+
files=item.children,
|
|
428
|
+
instance_path=elem.instance_path,
|
|
429
|
+
container_path=elem.container_path,
|
|
430
|
+
copy_only_folder_contents=copy_only_folder_contents,
|
|
431
|
+
has_parent=True,
|
|
432
|
+
))
|
|
433
|
+
|
|
434
|
+
result.append(elem)
|
|
435
|
+
|
|
436
|
+
return result
|
|
437
|
+
|
|
438
|
+
def upload_data_to_container(
|
|
439
|
+
self,
|
|
440
|
+
ip_address: str,
|
|
441
|
+
username: str,
|
|
442
|
+
src_path: str,
|
|
443
|
+
dest_path: str,
|
|
444
|
+
instance_path: str,
|
|
445
|
+
container_path: str,
|
|
446
|
+
copy_only_folder_contents: bool,
|
|
447
|
+
private_key_path: Optional[str],
|
|
448
|
+
):
|
|
449
|
+
has_error = False
|
|
450
|
+
client, sftp = self.__build_sftp_client(username=username, ip_address=ip_address, private_key_path=private_key_path)
|
|
451
|
+
|
|
452
|
+
origin_files: List[FileItemEntity] = self.__file_system_service.get_path_items(src_path)
|
|
453
|
+
|
|
454
|
+
files: List[SftpFileItemEntity] = self.__build_path_mapping_for_upload(
|
|
455
|
+
files=origin_files,
|
|
456
|
+
instance_path=instance_path,
|
|
457
|
+
container_path=container_path,
|
|
458
|
+
copy_only_folder_contents=copy_only_folder_contents
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
try:
|
|
462
|
+
for item in files:
|
|
463
|
+
self.__upload_list_files(
|
|
464
|
+
sftp=sftp,
|
|
465
|
+
src_item=item,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
if len(files) == 0:
|
|
469
|
+
typer.echo(__("No source files could be found on the server"))
|
|
470
|
+
raise typer.Exit(1)
|
|
471
|
+
|
|
472
|
+
if len(files[0].children) == 0 and files[0].is_folder:
|
|
473
|
+
typer.echo(__("Source directory is empty"))
|
|
474
|
+
raise typer.Exit(1)
|
|
475
|
+
|
|
476
|
+
except FileNotFoundError as err:
|
|
477
|
+
app_logger.error(f"Error uploading file to container {ip_address}, user {username} (file not found): {err}")
|
|
478
|
+
typer.echo(__("Error uploading file: file not found on server"))
|
|
479
|
+
has_error = True
|
|
480
|
+
finally:
|
|
481
|
+
sftp.close()
|
|
482
|
+
|
|
483
|
+
client.close()
|
|
484
|
+
if has_error:
|
|
485
|
+
raise typer.Exit(1)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def __read_remote_path_items_for_download(
|
|
489
|
+
self,
|
|
490
|
+
sftp: SFTPClient,
|
|
491
|
+
current_path: str,
|
|
492
|
+
dest_path: str,
|
|
493
|
+
instance_path: str,
|
|
494
|
+
copy_only_folder_contents: bool,
|
|
495
|
+
depth: int = 0,
|
|
496
|
+
) -> List[SftpFileItemEntity]:
|
|
497
|
+
config = self.__config_provider.get_config()
|
|
498
|
+
path_items = []
|
|
499
|
+
try:
|
|
500
|
+
root_stat = sftp.stat(current_path)
|
|
501
|
+
parent = SftpFileItemEntity(
|
|
502
|
+
name=current_path.split('/')[-1],
|
|
503
|
+
path=current_path,
|
|
504
|
+
is_file=stat.S_ISREG(root_stat.st_mode),
|
|
505
|
+
is_folder=stat.S_ISDIR(root_stat.st_mode),
|
|
506
|
+
file_size=root_stat.st_size,
|
|
507
|
+
instance_path=instance_path,
|
|
508
|
+
dest_path=dest_path,
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
if depth == 0 and not copy_only_folder_contents and parent.is_folder:
|
|
512
|
+
parent.dest_path = parent.dest_path.rstrip("/") + "/" + parent.name
|
|
513
|
+
path_items.append(parent)
|
|
514
|
+
if parent.is_file:
|
|
515
|
+
# must be true if destination is file
|
|
516
|
+
# must be false if destination is dir
|
|
517
|
+
# if destination does not exist, destination is file (true)
|
|
518
|
+
treat_as_file = True
|
|
519
|
+
dest_fullpath = Path(config.runtime.working_directory + '/' + dest_path)
|
|
520
|
+
if dest_fullpath.exists() and dest_fullpath.is_dir():
|
|
521
|
+
treat_as_file = False
|
|
522
|
+
|
|
523
|
+
if not treat_as_file:
|
|
524
|
+
parent.dest_path += f"{parent.name}" if parent.dest_path.endswith('/') else f"/{parent.name}"
|
|
525
|
+
|
|
526
|
+
elif parent.is_folder:
|
|
527
|
+
sftp.chdir(current_path)
|
|
528
|
+
if depth > 0:
|
|
529
|
+
parent.dest_path += f"{parent.name}" if parent.dest_path.endswith('/') else f"/{parent.name}"
|
|
530
|
+
for item in sftp.listdir_attr():
|
|
531
|
+
next_path = f'{current_path}/{item.filename}'
|
|
532
|
+
is_dir = stat.S_ISDIR(item.st_mode)
|
|
533
|
+
is_file = stat.S_ISREG(item.st_mode)
|
|
534
|
+
if is_file:
|
|
535
|
+
parent.children.append(SftpFileItemEntity(
|
|
536
|
+
name=item.filename,
|
|
537
|
+
path=next_path,
|
|
538
|
+
is_file=is_file,
|
|
539
|
+
is_folder=is_dir,
|
|
540
|
+
file_size=item.st_size,
|
|
541
|
+
instance_path=f'{instance_path}/{item.filename}',
|
|
542
|
+
dest_path=f'{parent.dest_path}/{item.filename}',
|
|
543
|
+
))
|
|
544
|
+
elif is_dir:
|
|
545
|
+
parent.children.extend(self.__read_remote_path_items_for_download(
|
|
546
|
+
sftp=sftp,
|
|
547
|
+
current_path=next_path,
|
|
548
|
+
dest_path=parent.dest_path,
|
|
549
|
+
instance_path=parent.instance_path,
|
|
550
|
+
depth=depth + 1,
|
|
551
|
+
copy_only_folder_contents=copy_only_folder_contents,
|
|
552
|
+
))
|
|
553
|
+
return path_items
|
|
554
|
+
except FileNotFoundError as ex:
|
|
555
|
+
app_logger.exception(f"Unable to read remote file list: {ex}")
|
|
556
|
+
typer.echo(__("Could not find the requested object on remote instance: %path%", {'path': current_path}))
|
|
557
|
+
raise typer.Exit(1)
|
|
558
|
+
except Exception as ex:
|
|
559
|
+
app_logger.exception(f"Error occurred: {ex}")
|
|
560
|
+
typer.echo(__('Error occurred while processing the file'))
|
|
561
|
+
raise typer.Exit(1)
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def download_data_from_container(
|
|
565
|
+
self,
|
|
566
|
+
ip_address: str,
|
|
567
|
+
username: str,
|
|
568
|
+
dest_path: str,
|
|
569
|
+
instance_path: str,
|
|
570
|
+
copy_only_folder_contents: bool,
|
|
571
|
+
private_key_path: Optional[str],
|
|
572
|
+
):
|
|
573
|
+
has_error = False
|
|
574
|
+
|
|
575
|
+
client, sftp = self.__build_sftp_client(username=username, ip_address=ip_address, private_key_path=private_key_path)
|
|
576
|
+
|
|
577
|
+
try:
|
|
578
|
+
files: List[SftpFileItemEntity] = self.__read_remote_path_items_for_download(
|
|
579
|
+
sftp=sftp,
|
|
580
|
+
current_path=instance_path,
|
|
581
|
+
instance_path=instance_path,
|
|
582
|
+
dest_path=dest_path,
|
|
583
|
+
copy_only_folder_contents=copy_only_folder_contents,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
if len(files) == 0:
|
|
587
|
+
typer.echo(__("No source files could be found on the server"))
|
|
588
|
+
raise typer.Exit(1)
|
|
589
|
+
|
|
590
|
+
if len(files[0].children) == 0 and files[0].is_folder:
|
|
591
|
+
typer.echo(__("Source directory is empty"))
|
|
592
|
+
raise typer.Exit(1)
|
|
593
|
+
|
|
594
|
+
for item in files:
|
|
595
|
+
self.__download_list_files(
|
|
596
|
+
sftp=sftp,
|
|
597
|
+
src_item=item,
|
|
598
|
+
)
|
|
599
|
+
except FileNotFoundError as err:
|
|
600
|
+
print(err)
|
|
601
|
+
app_logger.error(f"Error uploading file to container {ip_address} for user {username} (file not found): {err}")
|
|
602
|
+
typer.echo(__("Error uploading file: file not found on server"))
|
|
603
|
+
has_error = True
|
|
604
|
+
finally:
|
|
605
|
+
sftp.close()
|
|
606
|
+
|
|
607
|
+
client.close()
|
|
608
|
+
if has_error:
|
|
609
|
+
raise typer.Exit(1)
|