thestage 0.6.6__py3-none-any.whl → 0.6.8__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 (194) hide show
  1. thestage/__init__.py +1 -1
  2. thestage/cli_command_helper.py +2 -2
  3. thestage/config/__init__.py +1 -1
  4. thestage/{services → config/business}/app_config_service.py +3 -4
  5. thestage/{services/config_provider → config/business}/config_provider.py +6 -5
  6. thestage/config/{config_storage.py → business/config_storage.py} +1 -1
  7. thestage/{services → config/business}/validation_service.py +9 -10
  8. thestage/{controllers/config_controller.py → config/communication/config_command.py} +7 -7
  9. thestage/{services/connect → connect/business}/connect_service.py +25 -22
  10. thestage/{services → connect/business}/remote_server_service.py +4 -5
  11. thestage/connect/communication/connect_api_client.py +84 -0
  12. thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_key_to_user_response.py +0 -1
  13. thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_public_key_to_instance_response.py +1 -4
  14. thestage/{services/clients/thestage_api/dtos/base_controller → connect/dto}/connect_resolve_response.py +1 -1
  15. thestage/controllers/base_controller.py +2 -2
  16. thestage/debug_main.dist.py +16 -14
  17. thestage/{services/container → docker_container/business}/container_service.py +120 -40
  18. thestage/{services/container → docker_container/business}/mapper/container_mapper.py +3 -3
  19. thestage/docker_container/communication/__init__.py +0 -0
  20. thestage/{controllers/container_controller.py → docker_container/communication/docker_command.py} +27 -86
  21. thestage/docker_container/communication/docker_container_api_client.py +99 -0
  22. thestage/docker_container/dto/__init__.py +0 -0
  23. thestage/docker_container/dto/container_action_request.py +11 -0
  24. thestage/{services/clients/thestage_api/dtos → docker_container/dto}/container_response.py +4 -4
  25. thestage/{services/clients/thestage_api/dtos/docker_container_controller → docker_container/dto}/docker_container_list_response.py +3 -5
  26. thestage/docker_container/dto/enum/__init__.py +0 -0
  27. thestage/git/__init__.py +0 -0
  28. thestage/git/business/__init__.py +0 -0
  29. thestage/git/communication/__init__.py +0 -0
  30. thestage/{services/clients/git → git/communication}/git_client.py +5 -5
  31. thestage/global_dto/__init__.py +0 -0
  32. thestage/global_dto/enums/__init__.py +0 -0
  33. thestage/helpers/error_handler.py +5 -5
  34. thestage/helpers/logger/app_logger.py +1 -3
  35. thestage/i18n/en_GB/messages.po +14 -14
  36. thestage/inference_model/__init__.py +0 -0
  37. thestage/inference_model/business/__init__.py +0 -0
  38. thestage/inference_model/business/inference_model_service.py +281 -0
  39. thestage/inference_model/business/mapper/__init__.py +0 -0
  40. thestage/{services/project/mapper/project_inference_simulator_model_mapper.py → inference_model/business/mapper/inference_model_mapper.py} +5 -5
  41. thestage/inference_model/communication/__init__.py +0 -0
  42. thestage/inference_model/communication/inference_model_api_client.py +139 -0
  43. thestage/inference_model/communication/inference_model_command.py +246 -0
  44. thestage/inference_model/dto/__init__.py +0 -0
  45. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_instance_request.py +0 -2
  46. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_instance_response.py +2 -1
  47. thestage/inference_model/dto/enum/__init__.py +0 -0
  48. thestage/{services/project/dto/inference_simulator_model_dto.py → inference_model/dto/inference_model.py} +1 -1
  49. thestage/{entities/project_inference_simulator_model.py → inference_model/dto/inference_model_entity.py} +2 -2
  50. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/inference_simulator_model_list_for_project_response.py +2 -3
  51. thestage/{services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_request.py → inference_model/dto/push_inference_simulator_model_request.py} +1 -1
  52. thestage/{services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_response.py → inference_model/dto/push_inference_simulator_model_response.py} +1 -1
  53. thestage/inference_simulator/__init__.py +0 -0
  54. thestage/inference_simulator/business/__init__.py +0 -0
  55. thestage/inference_simulator/business/inference_simulator_service.py +338 -0
  56. thestage/inference_simulator/business/mapper/__init__.py +0 -0
  57. thestage/{services/project/mapper/project_inference_simulator_mapper.py → inference_simulator/business/mapper/inference_simulator_mapper.py} +5 -5
  58. thestage/inference_simulator/communication/__init__.py +0 -0
  59. thestage/inference_simulator/communication/inference_simulator_api_client.py +114 -0
  60. thestage/inference_simulator/communication/inference_simulator_command.py +347 -0
  61. thestage/inference_simulator/dto/__init__.py +0 -0
  62. thestage/inference_simulator/dto/enum/__init__.py +0 -0
  63. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/get_inference_simulator_request.py +1 -1
  64. thestage/inference_simulator/dto/get_inference_simulator_response.py +12 -0
  65. thestage/{services/project/dto/inference_simulator_dto.py → inference_simulator/dto/inference_simulator.py} +1 -1
  66. thestage/{entities/project_inference_simulator.py → inference_simulator/dto/inference_simulator_entity.py} +1 -1
  67. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/inference_simulator_list_response.py +2 -2
  68. thestage/{services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_request.py → inference_simulator/dto/start_inference_simulator_request.py} +1 -1
  69. thestage/inference_simulator/dto/start_inference_simulator_response.py +10 -0
  70. thestage/instance/__init__.py +0 -0
  71. thestage/instance/business/__init__.py +0 -0
  72. thestage/{services/instance → instance/business}/instance_service.py +26 -27
  73. thestage/instance/business/mapper/__init__.py +0 -0
  74. thestage/{services/instance/mapper/instance_mapper.py → instance/business/mapper/rented_instance_mapper.py} +3 -3
  75. thestage/{services/instance/mapper/selfhosted_mapper.py → instance/business/mapper/selfhosted_instance_mapper.py} +5 -7
  76. thestage/instance/communication/__init__.py +0 -0
  77. thestage/instance/communication/instance_api_client.py +150 -0
  78. thestage/{controllers/instance_controller.py → instance/communication/instance_command.py} +9 -9
  79. thestage/instance/dto/__init__.py +0 -0
  80. thestage/instance/dto/enum/__init__.py +0 -0
  81. thestage/{services/clients/thestage_api/dtos → instance/dto}/instance_detected_gpus.py +1 -2
  82. thestage/{services/clients/thestage_api/dtos → instance/dto}/instance_rented_response.py +2 -2
  83. thestage/{services/clients/thestage_api/dtos → instance/dto}/selfhosted_instance_response.py +2 -3
  84. thestage/logging/__init__.py +0 -0
  85. thestage/logging/business/__init__.py +0 -0
  86. thestage/{services/logging → logging/business}/logging_service.py +45 -36
  87. thestage/logging/communication/__init__.py +0 -0
  88. thestage/logging/communication/logging_api_client.py +63 -0
  89. thestage/logging/dto/__init__.py +0 -0
  90. thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/log_polling_response.py +2 -2
  91. thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/user_logs_query_response.py +2 -2
  92. thestage/main.py +48 -9
  93. thestage/project/__init__.py +0 -0
  94. thestage/project/business/__init__.py +0 -0
  95. thestage/project/business/project_service.py +480 -0
  96. thestage/project/communication/__init__.py +0 -0
  97. thestage/project/communication/project_api_client.py +46 -0
  98. thestage/project/communication/project_command.py +284 -0
  99. thestage/project/dto/__init__.py +0 -0
  100. thestage/{services/project → project}/dto/project_config.py +1 -2
  101. thestage/services/clients/thestage_api/core/api_client_core.py +1 -1
  102. thestage/services/clients/thestage_api/dtos/entity_filter_request.py +1 -1
  103. thestage/services/clients/thestage_api/dtos/sftp_path_helper.py +1 -1
  104. thestage/services/filesystem_service.py +2 -2
  105. thestage/services/service_factory.py +130 -43
  106. thestage/task/__init__.py +0 -0
  107. thestage/task/business/__init__.py +0 -0
  108. thestage/task/business/mapper/__init__.py +0 -0
  109. thestage/{services/project/mapper/project_task_mapper.py → task/business/mapper/task_mapper.py} +5 -5
  110. thestage/task/business/task_service.py +304 -0
  111. thestage/task/communication/__init__.py +0 -0
  112. thestage/task/communication/task_api_client.py +122 -0
  113. thestage/task/communication/task_command.py +212 -0
  114. thestage/task/dto/__init__.py +0 -0
  115. thestage/task/dto/enum/__init__.py +0 -0
  116. thestage/{services/clients/thestage_api/dtos/task_controller/task_list_for_project_response.py → task/dto/list_for_project_response.py} +2 -2
  117. thestage/{services/clients/thestage_api/dtos/project_controller/project_run_task_request.py → task/dto/run_task_request.py} +1 -1
  118. thestage/task/dto/run_task_response.py +13 -0
  119. thestage/{services/task/dto/task_dto.py → task/dto/task.py} +1 -4
  120. thestage/{entities/project_task.py → task/dto/task_entity.py} +1 -1
  121. thestage/{services/clients/thestage_api/dtos/task_controller/task_view_response.py → task/dto/view_response.py} +2 -2
  122. {thestage-0.6.6.dist-info → thestage-0.6.8.dist-info}/METADATA +2 -1
  123. thestage-0.6.8.dist-info/RECORD +219 -0
  124. {thestage-0.6.6.dist-info → thestage-0.6.8.dist-info}/WHEEL +1 -1
  125. thestage/controllers/project_controller.py +0 -1058
  126. thestage/services/clients/thestage_api/api_client.py +0 -753
  127. thestage/services/clients/thestage_api/dtos/container_param_request.py +0 -11
  128. thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_response.py +0 -13
  129. thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_response.py +0 -10
  130. thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_response.py +0 -10
  131. thestage/services/clients/thestage_api/dtos/user_controller/user_profile.py +0 -12
  132. thestage/services/project/project_service.py +0 -1283
  133. thestage-0.6.6.dist-info/RECORD +0 -167
  134. /thestage/{entities → color_scheme}/__init__.py +0 -0
  135. /thestage/{entities/enums → config/business}/__init__.py +0 -0
  136. /thestage/{services/clients/git → config/communication}/__init__.py +0 -0
  137. /thestage/{services/clients/thestage_api/dtos/enums → config/dto}/__init__.py +0 -0
  138. /thestage/{services/core_files → config/dto}/config_entity.py +0 -0
  139. /thestage/{services/connect → config}/dto/remote_server_config.py +0 -0
  140. /thestage/{services/config_provider → connect}/__init__.py +0 -0
  141. /thestage/{services/container → connect/business}/__init__.py +0 -0
  142. /thestage/{services/container/mapper → connect/communication}/__init__.py +0 -0
  143. /thestage/{services/instance → connect/dto}/__init__.py +0 -0
  144. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_key_to_user_request.py +0 -0
  145. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_public_key_to_instance_request.py +0 -0
  146. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/is_user_has_public_ssh_key_request.py +0 -0
  147. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/is_user_has_public_ssh_key_response.py +0 -0
  148. /thestage/{services/instance/mapper → docker_container}/__init__.py +0 -0
  149. /thestage/{services/project → docker_container/business}/__init__.py +0 -0
  150. /thestage/{services/project → docker_container/business}/mapper/__init__.py +0 -0
  151. /thestage/{entities/container.py → docker_container/dto/container_entity.py} +0 -0
  152. /thestage/{services/clients/thestage_api/dtos/docker_container_controller → docker_container/dto}/docker_container_list_request.py +0 -0
  153. /thestage/{services/clients/thestage_api/dtos → docker_container/dto}/docker_container_mapping.py +0 -0
  154. /thestage/{services/clients/thestage_api/dtos/enums → docker_container/dto/enum}/container_pending_action.py +0 -0
  155. /thestage/{services/clients/thestage_api/dtos/enums → docker_container/dto/enum}/container_status.py +0 -0
  156. /thestage/{services/logging/exception → exceptions}/log_polling_exception.py +0 -0
  157. /thestage/git/{ProgressPrinter.py → business/ProgressPrinter.py} +0 -0
  158. /thestage/{entities → global_dto}/enums/order_direction_type.py +0 -0
  159. /thestage/{entities → global_dto}/enums/shell_type.py +0 -0
  160. /thestage/{entities → global_dto}/enums/tail_output_type.py +0 -0
  161. /thestage/{entities → global_dto}/enums/yes_no_response.py +0 -0
  162. /thestage/{entities → global_dto}/file_item.py +0 -0
  163. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_sagemaker_request.py +0 -0
  164. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_sagemaker_response.py +0 -0
  165. /thestage/{services/clients/thestage_api/dtos/enums → inference_model/dto/enum}/inference_model_status.py +0 -0
  166. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/inference_simulator_model_list_for_project_request.py +0 -0
  167. /thestage/{services/clients/thestage_api/dtos → inference_model/dto}/inference_simulator_model_response.py +0 -0
  168. /thestage/{services/clients/thestage_api/dtos/enums → inference_simulator/dto/enum}/inference_simulator_status.py +0 -0
  169. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/inference_simulator_list_request.py +0 -0
  170. /thestage/{services/clients/thestage_api/dtos → inference_simulator/dto}/inference_simulator_response.py +0 -0
  171. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/cpu_type.py +0 -0
  172. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/gpu_name.py +0 -0
  173. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/instance_rented_status.py +0 -0
  174. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/provider_name.py +0 -0
  175. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/selfhosted_status.py +0 -0
  176. /thestage/{entities → instance/dto}/rented_instance.py +0 -0
  177. /thestage/{entities → instance/dto}/self_hosted_instance.py +0 -0
  178. /thestage/{services/logging → logging}/byte_print_style.py +0 -0
  179. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/docker_container_log_stream_request.py +0 -0
  180. /thestage/{services/logging → logging}/dto/log_message.py +0 -0
  181. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/log_polling_request.py +0 -0
  182. /thestage/{services/logging → logging}/dto/log_type.py +0 -0
  183. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/task_log_stream_request.py +0 -0
  184. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/user_logs_query_request.py +0 -0
  185. /thestage/{services/logging → logging}/logging_constants.py +0 -0
  186. /thestage/{services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_request.py → project/dto/get_deploy_ssh_key_request.py} +0 -0
  187. /thestage/{services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_response.py → project/dto/get_deploy_ssh_key_response.py} +0 -0
  188. /thestage/{services/clients/thestage_api/dtos → project/dto}/project_response.py +0 -0
  189. /thestage/{services/clients/thestage_api/dtos/enums → task/dto/enum}/task_execution_status.py +0 -0
  190. /thestage/{services/clients/thestage_api/dtos/enums → task/dto/enum}/task_status.py +0 -0
  191. /thestage/{services/clients/thestage_api/dtos/task_controller/task_list_for_project_request.py → task/dto/list_for_project_request.py} +0 -0
  192. /thestage/{services/clients/thestage_api/dtos/task_controller/task_status_localized_map_response.py → task/dto/status_localized_map_response.py} +0 -0
  193. {thestage-0.6.6.dist-info → thestage-0.6.8.dist-info}/entry_points.txt +0 -0
  194. {thestage-0.6.6.dist-info → thestage-0.6.8.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,38 +1,47 @@
1
+ import re
1
2
  from pathlib import Path
2
3
  from typing import List, Tuple, Optional, Dict
4
+ from rich import print
3
5
 
4
6
  import typer
5
- from thestage.entities.container import DockerContainerEntity
6
- from thestage.services.clients.thestage_api.dtos.container_param_request import DockerContainerActionRequestDto
7
- from thestage.services.clients.thestage_api.dtos.enums.container_pending_action import DockerContainerAction
8
- from thestage.services.clients.thestage_api.dtos.enums.container_status import DockerContainerStatus
9
- from thestage.entities.enums.shell_type import ShellType
7
+
8
+ from thestage.color_scheme.color_scheme import ColorScheme
9
+ from thestage.connect.communication.connect_api_client import ConnectApiClient
10
+ from thestage.docker_container.communication.docker_container_api_client import DockerContainerApiClient
11
+ from thestage.docker_container.dto.container_entity import DockerContainerEntity
12
+ from thestage.docker_container.dto.container_action_request import DockerContainerActionRequest
13
+ from thestage.docker_container.dto.enum.container_pending_action import DockerContainerAction
14
+ from thestage.docker_container.dto.enum.container_status import DockerContainerStatus
15
+ from thestage.global_dto.enums.shell_type import ShellType
10
16
  from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
11
- from thestage.services.container.mapper.container_mapper import ContainerMapper
17
+ from thestage.docker_container.business.mapper.container_mapper import ContainerMapper
12
18
  from thestage.services.filesystem_service import FileSystemService
13
- from thestage.services.remote_server_service import RemoteServerService
19
+ from thestage.connect.business.remote_server_service import RemoteServerService
14
20
  from thestage.i18n.translation import __
15
21
  from thestage.services.abstract_service import AbstractService
16
- from thestage.services.clients.thestage_api.dtos.container_response import DockerContainerDto
22
+ from thestage.docker_container.dto.container_response import DockerContainerDto
17
23
  from thestage.helpers.error_handler import error_handler
18
- from thestage.services.clients.thestage_api.api_client import TheStageApiClient
19
- from thestage.services.config_provider.config_provider import ConfigProvider
24
+ from thestage.config.business.config_provider import ConfigProvider
20
25
 
21
26
 
22
27
  class ContainerService(AbstractService):
23
28
 
24
- __thestage_api_client: TheStageApiClient = None
29
+ __docker_container_api_client: DockerContainerApiClient = None
25
30
  __config_provider: ConfigProvider = None
31
+ __connect_api_client: ConnectApiClient = None
26
32
 
27
33
  def __init__(
28
34
  self,
29
- thestage_api_client: TheStageApiClient,
35
+ docker_container_api_client: DockerContainerApiClient,
30
36
  config_provider: ConfigProvider,
31
37
  remote_server_service: RemoteServerService,
32
38
  file_system_service: FileSystemService,
39
+ connect_api_client: ConnectApiClient,
40
+
33
41
  ):
34
42
  self.__config_provider = config_provider
35
- self.__thestage_api_client = thestage_api_client
43
+ self.__connect_api_client = connect_api_client
44
+ self.__docker_container_api_client = docker_container_api_client
36
45
  self.__remote_server_service = remote_server_service
37
46
  self.__file_system_service = file_system_service
38
47
 
@@ -46,7 +55,7 @@ class ContainerService(AbstractService):
46
55
  project_slug: Optional[str],
47
56
  statuses: List[str],
48
57
  ):
49
- container_status_map = self.__thestage_api_client.get_container_business_status_map()
58
+ container_status_map = self.__docker_container_api_client.get_container_business_status_map()
50
59
 
51
60
  if not statuses:
52
61
  statuses = ({key: container_status_map[key] for key in [
@@ -99,7 +108,7 @@ class ContainerService(AbstractService):
99
108
  project_slug: Optional[str] = None,
100
109
  ) -> PaginatedEntityList[DockerContainerDto]:
101
110
 
102
- list = self.__thestage_api_client.get_container_list(
111
+ list = self.__docker_container_api_client.get_container_list(
103
112
  statuses=statuses,
104
113
  page=page,
105
114
  limit=row,
@@ -116,7 +125,7 @@ class ContainerService(AbstractService):
116
125
  container_public_id: Optional[str] = None,
117
126
  container_slug: Optional[str] = None,
118
127
  ) -> Optional[DockerContainerDto]:
119
- return self.__thestage_api_client.get_container(
128
+ return self.__docker_container_api_client.get_container(
120
129
  container_public_id=container_public_id,
121
130
  container_slug=container_slug,
122
131
  )
@@ -150,7 +159,7 @@ class ContainerService(AbstractService):
150
159
  if private_key_path:
151
160
  typer.echo(f'Using configured private key for {ip_address}: {private_key_path}')
152
161
  else:
153
- typer.echo(f'Using SSH agent to connect to {ip_address}')
162
+ typer.echo(f'Using SSH agent to connect to {ip_address} as {username}')
154
163
  else:
155
164
  self.__config_provider.update_remote_server_config_entry(ip_address, Path(private_key_path))
156
165
  typer.echo(f'Updated private key path for {ip_address}: {private_key_path}')
@@ -267,22 +276,49 @@ class ContainerService(AbstractService):
267
276
  @error_handler()
268
277
  def put_file_to_container(
269
278
  self,
270
- container: DockerContainerDto,
271
- src_path: str,
272
- copy_only_folder_contents: bool,
273
- destination_path: Optional[str] = None,
274
- username_param: Optional[str] = None,
279
+ source_path: str,
280
+ destination: str,
281
+ username_param: Optional[str],
275
282
  ):
276
- if not self.__file_system_service.check_if_path_exist(file=src_path):
277
- typer.echo(__("File not found at specified path"))
283
+ container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", destination)
284
+ if container_args is None:
285
+ typer.echo(__('Container name and source file path are required as the second argument'))
286
+ typer.echo(__('Example: container_name:/path/to/file'))
278
287
  raise typer.Exit(1)
288
+ container_identifier = container_args.groups()[0]
289
+ destination_path = container_args.groups()[1].rstrip("/")
279
290
 
280
- username, ip_address, private_key_path = self.get_server_auth(
281
- container=container,
282
- username_param=username_param,
283
- private_key_path_override=None
291
+ if not container_identifier:
292
+ typer.echo('Container identifier (container_id_or_name) is required')
293
+ raise typer.Exit(1)
294
+
295
+ resolved_options = self.__connect_api_client.resolve_user_input(entity_identifier=container_identifier)
296
+ container_public_id = None
297
+ valid_container_count = 0
298
+ for container_item in resolved_options.dockerContainerMatchData:
299
+ message = f"Found a container with matching {container_item.matchedField} in status: '{container_item.frontendStatus.status_translation}' (ID: {container_item.publicId})"
300
+ line_color = ColorScheme.SUCCESS.value if container_item.canDownloadUploadOnContainer else 'default'
301
+ print(f"[{line_color}]{message}[{line_color}]")
302
+ if container_item.canDownloadUploadOnContainer:
303
+ valid_container_count += 1
304
+ container_public_id = container_item.publicId
305
+
306
+ if valid_container_count != 1:
307
+ typer.echo(f"Failed to resolve the container by provided identifier, as total of {valid_container_count} containers are valid options")
308
+ raise typer.Exit(1)
309
+
310
+ container: Optional[DockerContainerDto] = self.__docker_container_api_client.get_container(
311
+ container_public_id=container_public_id,
284
312
  )
285
313
 
314
+ if not container:
315
+ typer.echo(f"Unexpected error: container '{container_public_id}' not found")
316
+ raise typer.Exit(1)
317
+
318
+ if not self.__file_system_service.check_if_path_exist(file=source_path):
319
+ typer.echo(__("File not found at specified path"))
320
+ raise typer.Exit(1)
321
+
286
322
  if not container.mappings or not container.mappings.directory_mappings:
287
323
  typer.echo(__("Mapping folders not found"))
288
324
  raise typer.Exit(1)
@@ -296,10 +332,18 @@ class ContainerService(AbstractService):
296
332
  typer.echo(__("Cannot find matching container volume mapping for specified file path"))
297
333
  raise typer.Exit(1)
298
334
 
335
+ username, ip_address, private_key_path = self.get_server_auth(
336
+ container=container,
337
+ username_param=username_param,
338
+ private_key_path_override=None
339
+ )
340
+
341
+ copy_only_folder_contents = source_path.endswith("/")
342
+
299
343
  self.__remote_server_service.upload_data_to_container(
300
344
  ip_address=ip_address,
301
345
  username=username,
302
- src_path=src_path,
346
+ src_path=source_path,
303
347
  dest_path=destination_path,
304
348
  instance_path=instance_path,
305
349
  container_path=container_path,
@@ -310,31 +354,67 @@ class ContainerService(AbstractService):
310
354
  @error_handler()
311
355
  def get_file_from_container(
312
356
  self,
313
- container: DockerContainerDto,
314
- src_path: str,
315
- copy_only_folder_contents: bool,
316
- destination_path: Optional[str] = None,
357
+ source: str,
358
+ destination_path: str,
317
359
  username_param: Optional[str] = None,
318
360
  ):
319
- username, ip_address, private_key_path = self.get_server_auth(
320
- container=container,
321
- username_param=username_param,
322
- private_key_path_override=None,
361
+ container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", source)
362
+
363
+ if container_args is None:
364
+ typer.echo(__('Container name and source directory path are required as the first argument'))
365
+ typer.echo(__('Example: container_name:/path/to/file'))
366
+ raise typer.Exit(1)
367
+ container_identifier = container_args.groups()[0]
368
+ source_path = container_args.groups()[1]
369
+
370
+ if not container_identifier:
371
+ typer.echo('Container identifier (container_id_or_name) is required')
372
+ raise typer.Exit(1)
373
+
374
+ resolved_options = self.__connect_api_client.resolve_user_input(entity_identifier=container_identifier)
375
+ container_public_id = None
376
+ valid_container_count = 0
377
+ for container_item in resolved_options.dockerContainerMatchData:
378
+ message = f"Found a container with matching {container_item.matchedField} in status: '{container_item.frontendStatus.status_translation}' (ID: {container_item.publicId})"
379
+ line_color = ColorScheme.SUCCESS.value if container_item.canDownloadUploadOnContainer else 'default'
380
+ print(f"[{line_color}]{message}[{line_color}]")
381
+ if container_item.canDownloadUploadOnContainer:
382
+ valid_container_count += 1
383
+ container_public_id = container_item.publicId
384
+
385
+ if valid_container_count != 1:
386
+ typer.echo(f"Failed to resolve the container by provided identifier, as total of {valid_container_count} containers are valid options")
387
+ raise typer.Exit(1)
388
+
389
+ container: Optional[DockerContainerDto] = self.__docker_container_api_client.get_container(
390
+ container_public_id=container_public_id,
323
391
  )
324
392
 
393
+ if not container:
394
+ typer.echo(f"Unexpected error: container '{container_public_id}' not found")
395
+ raise typer.Exit(1)
396
+
325
397
  if not container.mappings or not container.mappings.directory_mappings:
326
398
  typer.echo(__("Mapping folders not found"))
327
399
  raise typer.Exit(1)
328
400
 
329
401
  instance_path, container_path = self._get_new_path_from_mapping(
330
402
  directory_mapping=container.mappings.directory_mappings,
331
- destination_path=src_path,
403
+ destination_path=source_path,
332
404
  )
333
405
 
334
406
  if not instance_path and not container_path:
335
407
  typer.echo(__("Cannot find matching container volume mapping for specified file path"))
336
408
  raise typer.Exit(1)
337
409
 
410
+ username, ip_address, private_key_path = self.get_server_auth(
411
+ container=container,
412
+ username_param=username_param,
413
+ private_key_path_override=None,
414
+ )
415
+
416
+ copy_only_folder_contents=source_path.endswith("/")
417
+
338
418
  self.__remote_server_service.download_data_from_container(
339
419
  ip_address=ip_address,
340
420
  username=username,
@@ -366,11 +446,11 @@ class ContainerService(AbstractService):
366
446
  if action in [DockerContainerAction.STOP, DockerContainerAction.RESTART]:
367
447
  self.check_if_container_running(container=container)
368
448
 
369
- request_params = DockerContainerActionRequestDto(
449
+ request_params = DockerContainerActionRequest(
370
450
  dockerContainerPublicId=container.public_id,
371
451
  action=action,
372
452
  )
373
- result = self.__thestage_api_client.container_action(
453
+ result = self.__docker_container_api_client.container_action(
374
454
  request_param=request_params,
375
455
  )
376
456
 
@@ -1,7 +1,7 @@
1
- from typing import Optional, Tuple
1
+ from typing import Optional
2
2
 
3
- from thestage.services.clients.thestage_api.dtos.container_response import DockerContainerDto
4
- from thestage.entities.container import DockerContainerEntity
3
+ from thestage.docker_container.dto.container_response import DockerContainerDto
4
+ from thestage.docker_container.dto.container_entity import DockerContainerEntity
5
5
  from thestage.services.abstract_mapper import AbstractMapper
6
6
 
7
7
 
File without changes
@@ -1,20 +1,17 @@
1
-
2
- import re
3
1
  from pathlib import Path
4
2
  from typing import Optional, List
5
3
 
4
+ import typer
5
+
6
6
  from thestage.cli_command import CliCommand
7
7
  from thestage.cli_command_helper import get_command_metadata, check_command_permission
8
- from thestage.services.clients.thestage_api.dtos.enums.container_pending_action import DockerContainerAction
9
- from thestage.services.clients.thestage_api.dtos.container_response import DockerContainerDto
10
- from thestage.i18n.translation import __
11
- from thestage.services.container.container_service import ContainerService
12
- from thestage.helpers.logger.app_logger import app_logger
13
8
  from thestage.controllers.utils_controller import validate_config_and_get_service_factory, get_current_directory
14
-
15
- import typer
16
-
17
- from thestage.services.logging.logging_service import LoggingService
9
+ from thestage.docker_container.business.container_service import ContainerService
10
+ from thestage.docker_container.dto.container_response import DockerContainerDto
11
+ from thestage.docker_container.dto.enum.container_pending_action import DockerContainerAction
12
+ from thestage.helpers.logger.app_logger import app_logger
13
+ from thestage.i18n.translation import __
14
+ from thestage.logging.business.logging_service import LoggingService
18
15
 
19
16
  app = typer.Typer(no_args_is_help=True, help=__("Manage containers"))
20
17
 
@@ -62,7 +59,7 @@ def list_containers(
62
59
  check_command_permission(command_name)
63
60
 
64
61
  if sum(v is not None for v in [project_public_id, project_slug]) > 1:
65
- typer.echo("Please provide a single identifier for project - ID or name.")
62
+ typer.echo("Provide a single identifier for project - ID or name.")
66
63
  raise typer.Exit(1)
67
64
 
68
65
  service_factory = validate_config_and_get_service_factory()
@@ -101,7 +98,7 @@ def container_info(
101
98
  check_command_permission(command_name)
102
99
 
103
100
  if sum(v is not None for v in [container_public_id, container_slug]) != 1:
104
- typer.echo("Please provide a single identifier for container - ID or name.")
101
+ typer.echo("Provide a single identifier for container - ID or name.")
105
102
  raise typer.Exit(1)
106
103
 
107
104
  service_factory = validate_config_and_get_service_factory()
@@ -191,7 +188,7 @@ def container_connect(
191
188
  check_command_permission(command_name)
192
189
 
193
190
  if sum(v is not None for v in [container_public_id, container_slug]) != 1:
194
- typer.echo("Please provide a single identifier for container - ID or name.")
191
+ typer.echo("Provide a single identifier for container - ID or name.")
195
192
  raise typer.Exit(1)
196
193
 
197
194
  if private_ssh_key_path and not Path(private_ssh_key_path).is_file():
@@ -215,7 +212,7 @@ def container_connect(
215
212
  @app.command(name="upload", no_args_is_help=True, help=__("Upload file to container"), **get_command_metadata(CliCommand.CONTAINER_UPLOAD))
216
213
  def upload_file(
217
214
  source_path: str = typer.Argument(help=__("Source file path"),),
218
- destination: Optional[str] = typer.Argument(help=__("Destination directory path in container. Format: container_name:/path/to/file"),),
215
+ destination: Optional[str] = typer.Argument(help=__("Destination directory path in container. Format: container_id_or_name:/path/to/file"),),
219
216
  username: Optional[str] = typer.Option(
220
217
  None,
221
218
  '--username',
@@ -228,50 +225,22 @@ def upload_file(
228
225
  app_logger.info(f'Running {command_name} from {get_current_directory()}')
229
226
  check_command_permission(command_name)
230
227
 
231
- container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", destination)
232
-
233
- if container_args is None:
234
- typer.echo(__('Container name and source file path are required as the second argument'))
235
- typer.echo(__('Example: container_uid:/path/to/file'))
236
- raise typer.Exit(1)
237
- container_slug = container_args.groups()[0]
238
- destination_path = container_args.groups()[1].rstrip("/")
239
-
240
- if not container_slug:
241
- typer.echo(__('Container name is required'))
242
- raise typer.Exit(1)
243
-
244
228
  service_factory = validate_config_and_get_service_factory()
245
229
  container_service: ContainerService = service_factory.get_container_service()
246
230
 
247
- container: Optional[DockerContainerDto] = container_service.get_container(
248
- container_slug=container_slug,
231
+ container_service.put_file_to_container(
232
+ source_path=source_path,
233
+ destination=destination,
234
+ username_param=username,
249
235
  )
250
236
 
251
- if container:
252
- container_service.check_if_container_running(
253
- container=container
254
- )
255
-
256
- typer.echo(f"Uploading file(s) to container '{container_slug}'")
257
-
258
- container_service.put_file_to_container(
259
- container=container,
260
- src_path=source_path,
261
- destination_path=destination_path,
262
- username_param=username,
263
- copy_only_folder_contents=source_path.endswith("/")
264
- )
265
- else:
266
- typer.echo(__("Container not found: %container_item%", {'container_item': container_slug}))
267
-
268
- app_logger.info(f'End send files to container')
237
+ app_logger.info(f'File upload completed')
269
238
  raise typer.Exit(0)
270
239
 
271
240
 
272
241
  @app.command(name="download", no_args_is_help=True, help=__("Download file from container"), **get_command_metadata(CliCommand.CONTAINER_DOWNLOAD))
273
242
  def download_file(
274
- source_path: str = typer.Argument(help=__("Source file path in container. Format: container_name:/path/to/file"),),
243
+ source: str = typer.Argument(help=__("Source file path in container. Format: container_name:/path/to/file"),),
275
244
  destination_path: str = typer.Argument(help=__("Destination directory path on local machine"),),
276
245
  username: Optional[str] = typer.Option(
277
246
  None,
@@ -285,44 +254,16 @@ def download_file(
285
254
  app_logger.info(f'Running {command_name} from {get_current_directory()}')
286
255
  check_command_permission(command_name)
287
256
 
288
- container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", source_path)
289
-
290
- if container_args is None:
291
- typer.echo(__('Container name and source directory path are required as the first argument'))
292
- typer.echo(__('Example: container-uid:/path/to/file'))
293
- raise typer.Exit(1)
294
- container_slug = container_args.groups()[0]
295
- source_path = container_args.groups()[1]
296
-
297
- if not container_slug:
298
- typer.echo(__('Container name is required'))
299
- raise typer.Exit(1)
300
-
301
257
  service_factory = validate_config_and_get_service_factory()
302
258
  container_service: ContainerService = service_factory.get_container_service()
303
259
 
304
- container: Optional[DockerContainerDto] = container_service.get_container(
305
- container_slug=container_slug,
260
+ container_service.get_file_from_container(
261
+ source=source,
262
+ destination_path=destination_path.rstrip("/"),
263
+ username_param=username,
306
264
  )
307
265
 
308
- if container:
309
- container_service.check_if_container_running(
310
- container=container
311
- )
312
-
313
- typer.echo(f"Downloading files from container: '{container_slug}'")
314
-
315
- container_service.get_file_from_container(
316
- container=container,
317
- src_path=source_path,
318
- destination_path=destination_path.rstrip("/"),
319
- username_param=username,
320
- copy_only_folder_contents=source_path.endswith("/"),
321
- )
322
- else:
323
- typer.echo(f"Container not found: {container_slug}")
324
-
325
- app_logger.info(f'End download files from container')
266
+ app_logger.info(f'File download completed')
326
267
  raise typer.Exit(0)
327
268
 
328
269
 
@@ -360,7 +301,7 @@ def start_container(
360
301
  action=DockerContainerAction.START
361
302
  )
362
303
 
363
- app_logger.info(f'End start container')
304
+ app_logger.info(f'Container start completed')
364
305
  raise typer.Exit(0)
365
306
 
366
307
 
@@ -398,7 +339,7 @@ def stop_container(
398
339
  action=DockerContainerAction.STOP
399
340
  )
400
341
 
401
- app_logger.info(f'End stop container')
342
+ app_logger.info(f'Container stop completed')
402
343
  raise typer.Exit(0)
403
344
 
404
345
 
@@ -436,7 +377,7 @@ def restart_container(
436
377
  action=DockerContainerAction.RESTART
437
378
  )
438
379
 
439
- app_logger.info(f'End stop container')
380
+ app_logger.info(f'Container restart completed')
440
381
  raise typer.Exit(0)
441
382
 
442
383
 
@@ -483,5 +424,5 @@ def container_logs(
483
424
  else:
484
425
  logging_service.print_last_container_logs(container_public_id=container_public_id, container_slug=container_slug, logs_number=logs_number)
485
426
 
486
- app_logger.info(f'Container logs - end')
427
+ app_logger.info(f'Container log streaming completed')
487
428
  raise typer.Exit(0)
@@ -0,0 +1,99 @@
1
+ from typing import Optional, List, Dict
2
+
3
+ from thestage.config.business.config_provider import ConfigProvider
4
+ from thestage.global_dto.enums.order_direction_type import OrderDirectionType
5
+ from thestage.services.clients.thestage_api.core.api_client_core import TheStageApiClientCore
6
+ from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
7
+ from thestage.services.clients.thestage_api.dtos.entity_filter_request import EntityFilterRequest
8
+ from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
9
+
10
+ from thestage.docker_container.dto.container_action_request import DockerContainerActionRequest
11
+ from thestage.docker_container.dto.container_response import (
12
+ DockerContainerDto,
13
+ DockerContainerViewResponse,
14
+ ContainerBusinessStatusMapperResponse
15
+ )
16
+ from thestage.docker_container.dto.docker_container_list_request import DockerContainerListRequest
17
+ from thestage.docker_container.dto.docker_container_list_response import DockerContainerListResponse
18
+
19
+
20
+ class DockerContainerApiClient(TheStageApiClientCore):
21
+ def __init__(self, config_provider: ConfigProvider):
22
+ super().__init__(url=config_provider.get_config().main.thestage_api_url)
23
+ self.__config_provider = config_provider
24
+
25
+ def get_container_list(
26
+ self,
27
+ project_public_id: Optional[str] = None,
28
+ project_slug: Optional[str] = None,
29
+ statuses: List[str] = [],
30
+ page: int = 1,
31
+ limit: int = 10,
32
+ ) -> PaginatedEntityList[DockerContainerDto]:
33
+ request = DockerContainerListRequest(
34
+ statuses=statuses,
35
+ projectPublicId=project_public_id,
36
+ projectSlug=project_slug,
37
+ entityFilterRequest=EntityFilterRequest(
38
+ orderByField="createdAt",
39
+ orderByDirection=OrderDirectionType.DESC,
40
+ page=page,
41
+ limit=limit,
42
+ ),
43
+ )
44
+
45
+ response = self._request(
46
+ method='POST',
47
+ url='/user-api/v2/docker-container/list',
48
+ data=request.model_dump(),
49
+ token=self.__config_provider.get_config().main.thestage_auth_token
50
+ )
51
+
52
+ result = DockerContainerListResponse.model_validate(response) if response else None
53
+ # return result.paginatedList.entities, result.paginatedList.pagination_data.total_pages if result and result.is_success else None
54
+ return result.paginatedList if result and result.is_success else None
55
+
56
+ def get_container(
57
+ self,
58
+ container_slug: Optional[str] = None,
59
+ container_public_id: Optional[str] = None,
60
+ ) -> Optional[DockerContainerDto]:
61
+ data = {
62
+ "dockerContainerPublicId": container_public_id,
63
+ "dockerContainerSlug": container_slug,
64
+ }
65
+
66
+ response = self._request(
67
+ method='POST',
68
+ url='/user-api/v2/docker-container/view',
69
+ data=data,
70
+ token=self.__config_provider.get_config().main.thestage_auth_token,
71
+ )
72
+
73
+ return DockerContainerViewResponse.model_validate(response).docker_container if response else None
74
+
75
+ def container_action(
76
+ self,
77
+ request_param: DockerContainerActionRequest,
78
+ ) -> TheStageBaseResponse:
79
+
80
+ response = self._request(
81
+ method='POST',
82
+ url='/user-api/v2/docker-container/action',
83
+ data=request_param.model_dump(by_alias=True),
84
+ token=self.__config_provider.get_config().main.thestage_auth_token,
85
+ )
86
+
87
+ result = TheStageBaseResponse.model_validate(response) if response else None
88
+ return result
89
+
90
+ def get_container_business_status_map(self) -> Optional[Dict[str, str]]:
91
+ response = self._request(
92
+ method='POST',
93
+ url='/user-api/v1/docker-container/status-localized-mapping',
94
+ data=None,
95
+ token=self.__config_provider.get_config().main.thestage_auth_token,
96
+ )
97
+
98
+ data = ContainerBusinessStatusMapperResponse.model_validate(response) if response else None
99
+ return data.docker_container_status_map if data else None
File without changes
@@ -0,0 +1,11 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import Field, BaseModel, ConfigDict
4
+
5
+ from thestage.docker_container.dto.enum.container_pending_action import DockerContainerAction
6
+
7
+ class DockerContainerActionRequest(BaseModel):
8
+ model_config = ConfigDict(use_enum_values=True)
9
+
10
+ dockerContainerPublicId: Optional[str] = Field(None, alias='dockerContainerPublicId')
11
+ action: DockerContainerAction = Field(None, alias='action')
@@ -2,11 +2,11 @@ from typing import Optional, List, Dict
2
2
 
3
3
  from pydantic import Field, BaseModel, ConfigDict
4
4
 
5
- from thestage.services.clients.thestage_api.dtos.docker_container_mapping import DockerContainerMappingDto
5
+ from thestage.docker_container.dto.docker_container_mapping import DockerContainerMappingDto
6
6
  from thestage.services.clients.thestage_api.dtos.frontend_status import FrontendStatusDto
7
- from thestage.services.clients.thestage_api.dtos.instance_rented_response import InstanceRentedDto
8
- from thestage.services.clients.thestage_api.dtos.project_response import ProjectDto
9
- from thestage.services.clients.thestage_api.dtos.selfhosted_instance_response import SelfHostedInstanceDto
7
+ from thestage.instance.dto.instance_rented_response import InstanceRentedDto
8
+ from thestage.project.dto.project_response import ProjectDto
9
+ from thestage.instance.dto.selfhosted_instance_response import SelfHostedInstanceDto
10
10
  from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse, TheStageBasePaginatedResponse
11
11
  from thestage.services.clients.thestage_api.dtos.pagination_data import PaginationData
12
12
 
@@ -1,11 +1,9 @@
1
- from typing import List, Optional
2
-
3
- from pydantic import Field, ConfigDict, BaseModel
1
+ from pydantic import Field, ConfigDict
4
2
 
5
3
  from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
6
- from thestage.services.clients.thestage_api.dtos.container_response import DockerContainerDto
4
+ from thestage.docker_container.dto.container_response import DockerContainerDto
7
5
  from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
8
- from thestage.services.clients.thestage_api.dtos.pagination_data import PaginationData
6
+
9
7
 
10
8
  class DockerContainerListResponse(TheStageBaseResponse):
11
9
  model_config = ConfigDict(use_enum_values=True)
File without changes
File without changes
File without changes
File without changes