thestage 0.5.47__py3-none-any.whl → 0.5.49__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 (179) hide show
  1. thestage/.env +5 -5
  2. thestage/__init__.py +3 -4
  3. thestage/__main__.py +9 -9
  4. thestage/cli_command.py +56 -56
  5. thestage/cli_command_helper.py +51 -51
  6. thestage/color_scheme/color_scheme.py +7 -7
  7. thestage/config/__init__.py +18 -18
  8. thestage/config/config_storage.py +5 -3
  9. thestage/config/env_base.py +7 -7
  10. thestage/controllers/__init__.py +0 -0
  11. thestage/controllers/base_controller.py +67 -67
  12. thestage/controllers/config_controller.py +137 -137
  13. thestage/controllers/container_controller.py +389 -389
  14. thestage/controllers/instance_controller.py +183 -183
  15. thestage/controllers/project_controller.py +802 -783
  16. thestage/controllers/utils_controller.py +32 -32
  17. thestage/debug_main.dist.py +28 -28
  18. thestage/entities/__init__.py +0 -0
  19. thestage/entities/container.py +17 -17
  20. thestage/entities/enums/__init__.py +0 -0
  21. thestage/entities/enums/order_direction_type.py +6 -6
  22. thestage/entities/enums/shell_type.py +7 -7
  23. thestage/entities/enums/tail_output_type.py +6 -6
  24. thestage/entities/enums/yes_no_response.py +7 -7
  25. thestage/entities/file_item.py +27 -27
  26. thestage/entities/project_inference_simulator.py +18 -18
  27. thestage/entities/project_inference_simulator_model.py +16 -16
  28. thestage/entities/project_task.py +19 -19
  29. thestage/entities/rented_instance.py +19 -19
  30. thestage/entities/self_hosted_instance.py +18 -18
  31. thestage/exceptions/__init__.py +0 -0
  32. thestage/exceptions/auth_exception.py +6 -6
  33. thestage/exceptions/base_exception.py +13 -13
  34. thestage/exceptions/business_logic_exception.py +6 -6
  35. thestage/exceptions/config_exception.py +6 -6
  36. thestage/exceptions/file_system_exception.py +6 -6
  37. thestage/exceptions/git_access_exception.py +17 -17
  38. thestage/exceptions/remote_server_exception.py +24 -24
  39. thestage/git/ProgressPrinter.py +22 -22
  40. thestage/helpers/__init__.py +0 -0
  41. thestage/helpers/error_handler.py +115 -115
  42. thestage/helpers/exception_hook.py +14 -14
  43. thestage/helpers/logger/__init__.py +0 -0
  44. thestage/helpers/logger/app_logger.py +50 -50
  45. thestage/helpers/ssh_util.py +38 -38
  46. thestage/i18n/en_GB/messages.po +947 -947
  47. thestage/i18n/translation.py +9 -9
  48. thestage/main.py +36 -36
  49. thestage/services/.env +6 -6
  50. thestage/services/__init__.py +0 -0
  51. thestage/services/abstract_mapper.py +9 -9
  52. thestage/services/abstract_service.py +87 -87
  53. thestage/services/app_config_service.py +52 -52
  54. thestage/services/clients/__init__.py +0 -0
  55. thestage/services/clients/git/__init__.py +0 -0
  56. thestage/services/clients/git/git_client.py +433 -331
  57. thestage/services/clients/thestage_api/__init__.py +0 -0
  58. thestage/services/clients/thestage_api/api_client.py +718 -720
  59. thestage/services/clients/thestage_api/core/api_client_core.py +108 -108
  60. thestage/services/clients/thestage_api/core/http_client_exception.py +12 -12
  61. thestage/services/clients/thestage_api/dtos/__init__.py +0 -0
  62. thestage/services/clients/thestage_api/dtos/base_response.py +13 -13
  63. thestage/services/clients/thestage_api/dtos/cloud_provider_region.py +19 -19
  64. thestage/services/clients/thestage_api/dtos/container_param_request.py +11 -11
  65. thestage/services/clients/thestage_api/dtos/container_response.py +67 -67
  66. thestage/services/clients/thestage_api/dtos/docker_container_assigned_device.py +10 -10
  67. thestage/services/clients/thestage_api/dtos/docker_container_controller/docker_container_list_request.py +13 -13
  68. thestage/services/clients/thestage_api/dtos/docker_container_controller/docker_container_list_response.py +13 -13
  69. thestage/services/clients/thestage_api/dtos/docker_container_mapping.py +10 -10
  70. thestage/services/clients/thestage_api/dtos/entity_filter_request.py +14 -14
  71. thestage/services/clients/thestage_api/dtos/enums/__init__.py +0 -0
  72. thestage/services/clients/thestage_api/dtos/enums/container_pending_action.py +10 -10
  73. thestage/services/clients/thestage_api/dtos/enums/container_status.py +17 -17
  74. thestage/services/clients/thestage_api/dtos/enums/cpu_type.py +8 -8
  75. thestage/services/clients/thestage_api/dtos/enums/currency_type.py +10 -10
  76. thestage/services/clients/thestage_api/dtos/enums/daemon_status.py +9 -9
  77. thestage/services/clients/thestage_api/dtos/enums/disk_type.py +7 -7
  78. thestage/services/clients/thestage_api/dtos/enums/drive_type.py +7 -7
  79. thestage/services/clients/thestage_api/dtos/enums/gpu_name.py +8 -8
  80. thestage/services/clients/thestage_api/dtos/enums/inference_model_status.py +9 -9
  81. thestage/services/clients/thestage_api/dtos/enums/inference_simulator_status.py +15 -15
  82. thestage/services/clients/thestage_api/dtos/enums/instance_rented_status.py +17 -17
  83. thestage/services/clients/thestage_api/dtos/enums/instance_type.py +7 -7
  84. thestage/services/clients/thestage_api/dtos/enums/location_region.py +11 -11
  85. thestage/services/clients/thestage_api/dtos/enums/power_status.py +10 -10
  86. thestage/services/clients/thestage_api/dtos/enums/provider_name.py +11 -11
  87. thestage/services/clients/thestage_api/dtos/enums/selfhosted_status.py +10 -10
  88. thestage/services/clients/thestage_api/dtos/enums/task_execution_status.py +12 -12
  89. thestage/services/clients/thestage_api/dtos/enums/task_status.py +12 -12
  90. thestage/services/clients/thestage_api/dtos/frontend_status.py +10 -10
  91. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_instance_request.py +13 -13
  92. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_instance_response.py +13 -13
  93. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_sagemaker_request.py +12 -12
  94. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_sagemaker_response.py +12 -12
  95. thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_request.py +10 -10
  96. thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_response.py +13 -13
  97. thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_list_for_project_request.py +14 -14
  98. thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_list_for_project_response.py +12 -12
  99. thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_model_list_for_project_request.py +12 -12
  100. thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_model_list_for_project_response.py +13 -13
  101. thestage/services/clients/thestage_api/dtos/inference_simulator_model_response.py +11 -11
  102. thestage/services/clients/thestage_api/dtos/inference_simulator_response.py +11 -11
  103. thestage/services/clients/thestage_api/dtos/installed_service.py +17 -17
  104. thestage/services/clients/thestage_api/dtos/instance_detected_gpus.py +20 -20
  105. thestage/services/clients/thestage_api/dtos/instance_rented_response.py +71 -71
  106. thestage/services/clients/thestage_api/dtos/logging_controller/docker_container_log_stream_request.py +7 -7
  107. thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_request.py +13 -13
  108. thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_response.py +14 -14
  109. thestage/services/clients/thestage_api/dtos/logging_controller/task_log_stream_request.py +7 -7
  110. thestage/services/clients/thestage_api/dtos/logging_controller/user_logs_query_request.py +21 -21
  111. thestage/services/clients/thestage_api/dtos/logging_controller/user_logs_query_response.py +14 -14
  112. thestage/services/clients/thestage_api/dtos/paginated_entity_list.py +11 -11
  113. thestage/services/clients/thestage_api/dtos/pagination_data.py +10 -10
  114. thestage/services/clients/thestage_api/dtos/price_definition.py +14 -14
  115. thestage/services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_request.py +7 -7
  116. thestage/services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_response.py +10 -10
  117. thestage/services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_request.py +8 -8
  118. thestage/services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_response.py +6 -6
  119. thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_request.py +15 -15
  120. thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_response.py +10 -10
  121. thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_request.py +13 -14
  122. thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_response.py +10 -10
  123. thestage/services/clients/thestage_api/dtos/project_response.py +32 -32
  124. thestage/services/clients/thestage_api/dtos/selfhosted_instance_response.py +56 -56
  125. thestage/services/clients/thestage_api/dtos/sftp_path_helper.py +13 -13
  126. thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_key_to_user_request.py +8 -8
  127. thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_key_to_user_response.py +11 -11
  128. thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_public_key_to_instance_request.py +8 -8
  129. thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_public_key_to_instance_response.py +11 -11
  130. thestage/services/clients/thestage_api/dtos/ssh_key_controller/is_user_has_public_ssh_key_request.py +7 -7
  131. thestage/services/clients/thestage_api/dtos/ssh_key_controller/is_user_has_public_ssh_key_response.py +12 -12
  132. thestage/services/clients/thestage_api/dtos/task_controller/task_list_for_project_request.py +10 -10
  133. thestage/services/clients/thestage_api/dtos/task_controller/task_list_for_project_response.py +12 -12
  134. thestage/services/clients/thestage_api/dtos/task_controller/task_status_localized_map_response.py +9 -9
  135. thestage/services/clients/thestage_api/dtos/task_controller/task_view_response.py +12 -12
  136. thestage/services/clients/thestage_api/dtos/user_controller/user_profile.py +12 -12
  137. thestage/services/clients/thestage_api/dtos/validate_token_response.py +11 -11
  138. thestage/services/config_provider/__init__.py +0 -0
  139. thestage/services/config_provider/config_provider.py +237 -237
  140. thestage/services/connect/connect_service.py +196 -196
  141. thestage/services/connect/dto/remote_server_config.py +9 -9
  142. thestage/services/container/__init__.py +0 -0
  143. thestage/services/container/container_service.py +374 -374
  144. thestage/services/container/mapper/__init__.py +0 -0
  145. thestage/services/container/mapper/container_mapper.py +30 -30
  146. thestage/services/core_files/config_entity.py +26 -26
  147. thestage/services/filesystem_service.py +133 -133
  148. thestage/services/instance/__init__.py +0 -0
  149. thestage/services/instance/instance_service.py +303 -303
  150. thestage/services/instance/mapper/__init__.py +0 -0
  151. thestage/services/instance/mapper/instance_mapper.py +24 -24
  152. thestage/services/instance/mapper/selfhosted_mapper.py +33 -33
  153. thestage/services/logging/byte_print_style.py +5 -5
  154. thestage/services/logging/dto/log_message.py +15 -15
  155. thestage/services/logging/dto/log_type.py +6 -6
  156. thestage/services/logging/exception/log_polling_exception.py +6 -6
  157. thestage/services/logging/logging_constants.py +3 -3
  158. thestage/services/logging/logging_service.py +367 -367
  159. thestage/services/project/__init__.py +0 -0
  160. thestage/services/project/dto/inference_simulator_dto.py +22 -22
  161. thestage/services/project/dto/inference_simulator_model_dto.py +20 -20
  162. thestage/services/project/dto/project_config.py +14 -14
  163. thestage/services/project/mapper/__init__.py +0 -0
  164. thestage/services/project/mapper/project_inference_simulator_mapper.py +21 -21
  165. thestage/services/project/mapper/project_inference_simulator_model_mapper.py +21 -21
  166. thestage/services/project/mapper/project_task_mapper.py +22 -22
  167. thestage/services/project/project_service.py +1260 -1241
  168. thestage/services/remote_server_service.py +609 -609
  169. thestage/services/service_factory.py +97 -97
  170. thestage/services/task/dto/task_dto.py +40 -40
  171. thestage/services/validation_service.py +61 -61
  172. {thestage-0.5.47.dist-info → thestage-0.5.49.dist-info}/LICENSE.txt +12 -12
  173. {thestage-0.5.47.dist-info → thestage-0.5.49.dist-info}/METADATA +1 -1
  174. thestage-0.5.49.dist-info/RECORD +176 -0
  175. {thestage-0.5.47.dist-info → thestage-0.5.49.dist-info}/WHEEL +1 -1
  176. thestage/debug_tests.py +0 -12
  177. thestage/services/clients/.DS_Store +0 -0
  178. thestage-0.5.47.dist-info/RECORD +0 -178
  179. {thestage-0.5.47.dist-info → thestage-0.5.49.dist-info}/entry_points.txt +0 -0
@@ -1,389 +1,389 @@
1
-
2
- import re
3
- from pathlib import Path
4
- from typing import Optional, List
5
-
6
- from thestage.cli_command import CliCommand
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
- 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
18
-
19
- app = typer.Typer(no_args_is_help=True, help=__("Manage containers"))
20
-
21
-
22
- @app.command(name='ls', help=__("List containers"), **get_command_metadata(CliCommand.CONTAINER_LS))
23
- def list_containers(
24
- row: int = typer.Option(
25
- 5,
26
- '--row',
27
- '-r',
28
- help=__("Set number of rows displayed per page"),
29
- is_eager=False,
30
- ),
31
- page: int = typer.Option(
32
- 1,
33
- '--page',
34
- '-p',
35
- help=__("Set starting page for displaying output"),
36
- is_eager=False,
37
- ),
38
- project_uid: str = typer.Option(
39
- None,
40
- '--project-uid',
41
- '-puid',
42
- help=__("Filter containers by project unique ID"),
43
- is_eager=False,
44
- ),
45
- statuses: List[str] = typer.Option(
46
- None,
47
- '--status',
48
- '-s',
49
- help=__("Filter by status, use --status all to list all containers"),
50
- is_eager=False,
51
- ),
52
- ):
53
- command_name = CliCommand.CONTAINER_LS
54
- app_logger.info(f'Running {command_name}')
55
- check_command_permission(command_name)
56
-
57
- service_factory = validate_config_and_get_service_factory()
58
- container_service: ContainerService = service_factory.get_container_service()
59
- container_service.print_container_list(
60
- row=row,
61
- page=page,
62
- project_uid=project_uid,
63
- statuses=statuses,
64
- )
65
-
66
- typer.echo(__("Containers listing complete"))
67
- raise typer.Exit(0)
68
-
69
-
70
- @app.command(name="info", no_args_is_help=True, help=__("Get container info"), **get_command_metadata(CliCommand.CONTAINER_INFO))
71
- def container_info(
72
- container_uid: Optional[str] = typer.Argument(hidden=False, help=__("Container unique ID")),
73
- ):
74
- command_name = CliCommand.CONTAINER_INFO
75
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
76
- check_command_permission(command_name)
77
-
78
- if not container_uid:
79
- typer.echo(__('Container unique ID is required'))
80
- raise typer.Exit(1)
81
-
82
- service_factory = validate_config_and_get_service_factory()
83
- container_service: ContainerService = service_factory.get_container_service()
84
-
85
- container: Optional[DockerContainerDto] = container_service.get_container(
86
- container_slug=container_uid,
87
- )
88
-
89
- if not container:
90
- typer.echo(__("Container not found: %container_item%", {'container_item': str(container_uid) if container_uid else ''}))
91
- raise typer.Exit(1)
92
-
93
- typer.echo(__("STATUS: %status%", {'status': str(container.frontend_status.status_translation if container and container.frontend_status else 'UNKNOWN')}))
94
- typer.echo(__("UNIQUE ID: %slug%", {'slug': str(container.slug)}))
95
- typer.echo(__("TITLE: %title%", {'title': str(container.title)}))
96
- typer.echo(__("IMAGE: %image%", {'image': str(container.docker_image)}))
97
-
98
- if container.instance_rented:
99
- typer.echo(
100
- __("RENTED SERVER INSTANCE UNIQUE ID: %instance_slug%", {'instance_slug': str(container.instance_rented.slug)})
101
- )
102
- typer.echo(
103
- __("RENTED SERVER INSTANCE STATUS: %instance_status%",
104
- {'instance_status': str(container.instance_rented.frontend_status.status_translation if container.instance_rented.frontend_status else 'UNKNOWN')})
105
- )
106
-
107
- if container.selfhosted_instance:
108
- typer.echo(
109
- __("SELF-HOSTED INSTANCE UNIQUE ID: %instance_slug%", {'instance_slug': str(container.selfhosted_instance.slug)})
110
- )
111
- typer.echo(
112
- __("SELF-HOSTED INSTANCE STATUS: %instance_status%",
113
- {'instance_status': str(container.selfhosted_instance.frontend_status.status_translation if container.selfhosted_instance.frontend_status else 'UNKNOWN')})
114
- )
115
-
116
- if container.mappings and (container.mappings.port_mappings or container.mappings.directory_mappings):
117
- if container.mappings.port_mappings:
118
- typer.echo(__("CONTAINER PORT MAPPING:"))
119
- for src, dest in container.mappings.port_mappings.items():
120
- typer.echo(f" {src} : {dest}")
121
-
122
- if container.mappings.directory_mappings:
123
- typer.echo(__("CONTAINER DIRECTORY MAPPING:"))
124
- for src, dest in container.mappings.directory_mappings.items():
125
- typer.echo(f" {src} : {dest}")
126
-
127
- raise typer.Exit(0)
128
-
129
-
130
- @app.command(name="connect", no_args_is_help=True, help=__("Connect to container"), **get_command_metadata(CliCommand.CONTAINER_CONNECT))
131
- def container_connect(
132
- container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"),),
133
- username: Optional[str] = typer.Option(
134
- None,
135
- '--username',
136
- '-u',
137
- help=__("Username for the server instance (required when connecting to self-hosted instance)"),
138
- is_eager=False,
139
- ),
140
- private_ssh_key_path: str = typer.Option(
141
- None,
142
- "--private-key-path",
143
- "-pk",
144
- help=__("Path to private key that will be accepted by remote server (optional)"),
145
- is_eager=False,
146
- ),
147
- ):
148
- command_name = CliCommand.CONTAINER_CONNECT
149
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
150
- check_command_permission(command_name)
151
-
152
- if not container_uid:
153
- typer.echo(__('Container unique ID is required'))
154
- raise typer.Exit(1)
155
-
156
- if private_ssh_key_path and not Path(private_ssh_key_path).is_file():
157
- typer.echo(f'No file found at provided path {private_ssh_key_path}')
158
- raise typer.Exit(1)
159
-
160
- service_factory = validate_config_and_get_service_factory()
161
- container_service: ContainerService = service_factory.get_container_service()
162
-
163
- container_service.connect_to_container(
164
- container_uid=container_uid,
165
- username=username,
166
- input_ssh_key_path=private_ssh_key_path,
167
- )
168
-
169
- app_logger.info(f'Stop connect to container')
170
- raise typer.Exit(0)
171
-
172
-
173
- @app.command(name="upload", no_args_is_help=True, help=__("Upload file to container"), **get_command_metadata(CliCommand.CONTAINER_UPLOAD))
174
- def upload_file(
175
- source_path: str = typer.Argument(help=__("Source file path"),),
176
- destination: Optional[str] = typer.Argument(help=__("Destination directory path in container. Format: container_uid:/path/to/file"),),
177
- username: Optional[str] = typer.Option(
178
- None,
179
- '--username',
180
- '-u',
181
- help=__("Username for the server instance (required when connecting to self-hosted instance)"),
182
- is_eager=False,
183
- ),
184
- ):
185
- command_name = CliCommand.CONTAINER_UPLOAD
186
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
187
- check_command_permission(command_name)
188
-
189
- container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", destination)
190
-
191
- if container_args is None:
192
- typer.echo(__('Container unique ID and source file path are required as the second argument'))
193
- typer.echo(__('Example: container_uid:/path/to/file'))
194
- raise typer.Exit(1)
195
- container_slug = container_args.groups()[0]
196
- destination_path = container_args.groups()[1].rstrip("/")
197
-
198
- if not container_slug:
199
- typer.echo(__('Container unique ID is required'))
200
- raise typer.Exit(1)
201
-
202
- service_factory = validate_config_and_get_service_factory()
203
- container_service: ContainerService = service_factory.get_container_service()
204
-
205
- container: Optional[DockerContainerDto] = container_service.get_container(
206
- container_slug=container_slug,
207
- )
208
-
209
- if container:
210
- container_service.check_if_container_running(
211
- container=container
212
- )
213
-
214
- typer.echo(__("Uploading file(s) to container '%container-slug%'", {'container-slug': container_slug}))
215
-
216
- container_service.put_file_to_container(
217
- container=container,
218
- src_path=source_path,
219
- destination_path=destination_path,
220
- username_param=username,
221
- copy_only_folder_contents=source_path.endswith("/")
222
- )
223
- else:
224
- typer.echo(__("Container not found: %container_item%", {'container_item': container_slug}))
225
-
226
- app_logger.info(f'End send files to container')
227
- raise typer.Exit(0)
228
-
229
-
230
- @app.command(name="download", no_args_is_help=True, help=__("Download file from container"), **get_command_metadata(CliCommand.CONTAINER_DOWNLOAD))
231
- def download_file(
232
- source_path: str = typer.Argument(help=__("Source file path in container. Format: container_uid:/path/to/file"),),
233
- destination_path: str = typer.Argument(help=__("Destination directory path on local machine"),),
234
- username: Optional[str] = typer.Option(
235
- None,
236
- '--username',
237
- '-u',
238
- help=__("Username for the server instance (required when connecting to self-hosted instance)"),
239
- is_eager=False,
240
- ),
241
- ):
242
- command_name = CliCommand.CONTAINER_DOWNLOAD
243
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
244
- check_command_permission(command_name)
245
-
246
- container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", source_path)
247
-
248
- if container_args is None:
249
- typer.echo(__('Container unique ID and source directory path are required as the first argument'))
250
- typer.echo(__('Example: container-uid:/path/to/file'))
251
- raise typer.Exit(1)
252
- container_slug = container_args.groups()[0]
253
- source_path = container_args.groups()[1]
254
-
255
- if not container_slug:
256
- typer.echo(__('Container unique ID is required'))
257
- raise typer.Exit(1)
258
-
259
- service_factory = validate_config_and_get_service_factory()
260
- container_service: ContainerService = service_factory.get_container_service()
261
-
262
- container: Optional[DockerContainerDto] = container_service.get_container(
263
- container_slug=container_slug,
264
- )
265
-
266
- if container:
267
- container_service.check_if_container_running(
268
- container=container
269
- )
270
-
271
- typer.echo(__("Downloading files from container: '%container-slug%'", {'container-slug': container_slug}))
272
-
273
- container_service.get_file_from_container(
274
- container=container,
275
- src_path=source_path,
276
- destination_path=destination_path.rstrip("/"),
277
- username_param=username,
278
- copy_only_folder_contents=source_path.endswith("/"),
279
- )
280
- else:
281
- typer.echo(__("Container not found: %container_item%", {'container_item': container_slug}))
282
-
283
- app_logger.info(f'End download files from container')
284
- raise typer.Exit(0)
285
-
286
-
287
- @app.command(name="start", no_args_is_help=True, help=__("Start container"), **get_command_metadata(CliCommand.CONTAINER_START))
288
- def start_container(
289
- container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"), ),
290
- ):
291
- command_name = CliCommand.CONTAINER_START
292
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
293
- check_command_permission(command_name)
294
-
295
- if not container_uid:
296
- typer.echo(__('Container unique ID is required'))
297
- raise typer.Exit(1)
298
-
299
- service_factory = validate_config_and_get_service_factory()
300
- container_service: ContainerService = service_factory.get_container_service()
301
-
302
- container_service.request_docker_container_action(
303
- container_uid=container_uid,
304
- action=DockerContainerAction.START
305
- )
306
-
307
- app_logger.info(f'End start container')
308
- raise typer.Exit(0)
309
-
310
-
311
- @app.command(name="stop", no_args_is_help=True, help=__("Stop container"), **get_command_metadata(CliCommand.CONTAINER_STOP))
312
- def stop_container(
313
- container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"), ),
314
- ):
315
- command_name = CliCommand.CONTAINER_STOP
316
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
317
- check_command_permission(command_name)
318
-
319
- if not container_uid:
320
- typer.echo(__('Container unique ID is required'))
321
- raise typer.Exit(1)
322
-
323
- service_factory = validate_config_and_get_service_factory()
324
- container_service: ContainerService = service_factory.get_container_service()
325
-
326
- container_service.request_docker_container_action(
327
- container_uid=container_uid,
328
- action=DockerContainerAction.STOP
329
- )
330
-
331
- app_logger.info(f'End stop container')
332
- raise typer.Exit(0)
333
-
334
-
335
- @app.command(name="restart", no_args_is_help=True, help=__("Restart container"), **get_command_metadata(CliCommand.CONTAINER_RESTART))
336
- def restart_container(
337
- container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"), ),
338
- ):
339
- command_name = CliCommand.CONTAINER_RESTART
340
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
341
- check_command_permission(command_name)
342
-
343
- if not container_uid:
344
- typer.echo(__('Container unique ID is required'))
345
- raise typer.Exit(1)
346
-
347
- service_factory = validate_config_and_get_service_factory()
348
- container_service: ContainerService = service_factory.get_container_service()
349
-
350
- container_service.request_docker_container_action(
351
- container_uid=container_uid,
352
- action=DockerContainerAction.RESTART
353
- )
354
-
355
- app_logger.info(f'End stop container')
356
- raise typer.Exit(0)
357
-
358
-
359
- @app.command(name="logs", no_args_is_help=True, help=__("Stream real-time Docker container logs or view last logs for a container"), **get_command_metadata(CliCommand.CONTAINER_LOGS))
360
- def container_logs(
361
- container_uid: Optional[str] = typer.Argument(help=__("Container unique id")),
362
- logs_number: Optional[int] = typer.Option(
363
- None,
364
- '--number',
365
- '-n',
366
- help=__("Display a number of latest log entries. No real-time stream if provided."),
367
- is_eager=False,
368
- ),
369
- ):
370
- command_name = CliCommand.CONTAINER_LOGS
371
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
372
- check_command_permission(command_name)
373
-
374
- if not container_uid:
375
- typer.echo(__('Container unique ID is required'))
376
- raise typer.Exit(1)
377
-
378
- service_factory = validate_config_and_get_service_factory()
379
- logging_service: LoggingService = service_factory.get_logging_service()
380
-
381
- if logs_number is None:
382
- logging_service.stream_container_logs_with_controls(
383
- container_uid=container_uid
384
- )
385
- else:
386
- logging_service.print_last_container_logs(container_uid=container_uid, logs_number=logs_number)
387
-
388
- app_logger.info(f'Container logs - end')
389
- raise typer.Exit(0)
1
+
2
+ import re
3
+ from pathlib import Path
4
+ from typing import Optional, List
5
+
6
+ from thestage.cli_command import CliCommand
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
+ 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
18
+
19
+ app = typer.Typer(no_args_is_help=True, help=__("Manage containers"))
20
+
21
+
22
+ @app.command(name='ls', help=__("List containers"), **get_command_metadata(CliCommand.CONTAINER_LS))
23
+ def list_containers(
24
+ row: int = typer.Option(
25
+ 5,
26
+ '--row',
27
+ '-r',
28
+ help=__("Set number of rows displayed per page"),
29
+ is_eager=False,
30
+ ),
31
+ page: int = typer.Option(
32
+ 1,
33
+ '--page',
34
+ '-p',
35
+ help=__("Set starting page for displaying output"),
36
+ is_eager=False,
37
+ ),
38
+ project_uid: str = typer.Option(
39
+ None,
40
+ '--project-uid',
41
+ '-puid',
42
+ help=__("Filter containers by project unique ID"),
43
+ is_eager=False,
44
+ ),
45
+ statuses: List[str] = typer.Option(
46
+ None,
47
+ '--status',
48
+ '-s',
49
+ help=__("Filter by status, use --status all to list all containers"),
50
+ is_eager=False,
51
+ ),
52
+ ):
53
+ command_name = CliCommand.CONTAINER_LS
54
+ app_logger.info(f'Running {command_name}')
55
+ check_command_permission(command_name)
56
+
57
+ service_factory = validate_config_and_get_service_factory()
58
+ container_service: ContainerService = service_factory.get_container_service()
59
+ container_service.print_container_list(
60
+ row=row,
61
+ page=page,
62
+ project_uid=project_uid,
63
+ statuses=statuses,
64
+ )
65
+
66
+ typer.echo(__("Containers listing complete"))
67
+ raise typer.Exit(0)
68
+
69
+
70
+ @app.command(name="info", no_args_is_help=True, help=__("Get container info"), **get_command_metadata(CliCommand.CONTAINER_INFO))
71
+ def container_info(
72
+ container_uid: Optional[str] = typer.Argument(hidden=False, help=__("Container unique ID")),
73
+ ):
74
+ command_name = CliCommand.CONTAINER_INFO
75
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
76
+ check_command_permission(command_name)
77
+
78
+ if not container_uid:
79
+ typer.echo(__('Container unique ID is required'))
80
+ raise typer.Exit(1)
81
+
82
+ service_factory = validate_config_and_get_service_factory()
83
+ container_service: ContainerService = service_factory.get_container_service()
84
+
85
+ container: Optional[DockerContainerDto] = container_service.get_container(
86
+ container_slug=container_uid,
87
+ )
88
+
89
+ if not container:
90
+ typer.echo(__("Container not found: %container_item%", {'container_item': str(container_uid) if container_uid else ''}))
91
+ raise typer.Exit(1)
92
+
93
+ typer.echo(__("STATUS: %status%", {'status': str(container.frontend_status.status_translation if container and container.frontend_status else 'UNKNOWN')}))
94
+ typer.echo(__("UNIQUE ID: %slug%", {'slug': str(container.slug)}))
95
+ typer.echo(__("TITLE: %title%", {'title': str(container.title)}))
96
+ typer.echo(__("IMAGE: %image%", {'image': str(container.docker_image)}))
97
+
98
+ if container.instance_rented:
99
+ typer.echo(
100
+ __("RENTED SERVER INSTANCE UNIQUE ID: %instance_slug%", {'instance_slug': str(container.instance_rented.slug)})
101
+ )
102
+ typer.echo(
103
+ __("RENTED SERVER INSTANCE STATUS: %instance_status%",
104
+ {'instance_status': str(container.instance_rented.frontend_status.status_translation if container.instance_rented.frontend_status else 'UNKNOWN')})
105
+ )
106
+
107
+ if container.selfhosted_instance:
108
+ typer.echo(
109
+ __("SELF-HOSTED INSTANCE UNIQUE ID: %instance_slug%", {'instance_slug': str(container.selfhosted_instance.slug)})
110
+ )
111
+ typer.echo(
112
+ __("SELF-HOSTED INSTANCE STATUS: %instance_status%",
113
+ {'instance_status': str(container.selfhosted_instance.frontend_status.status_translation if container.selfhosted_instance.frontend_status else 'UNKNOWN')})
114
+ )
115
+
116
+ if container.mappings and (container.mappings.port_mappings or container.mappings.directory_mappings):
117
+ if container.mappings.port_mappings:
118
+ typer.echo(__("CONTAINER PORT MAPPING:"))
119
+ for src, dest in container.mappings.port_mappings.items():
120
+ typer.echo(f" {src} : {dest}")
121
+
122
+ if container.mappings.directory_mappings:
123
+ typer.echo(__("CONTAINER DIRECTORY MAPPING:"))
124
+ for src, dest in container.mappings.directory_mappings.items():
125
+ typer.echo(f" {src} : {dest}")
126
+
127
+ raise typer.Exit(0)
128
+
129
+
130
+ @app.command(name="connect", no_args_is_help=True, help=__("Connect to container"), **get_command_metadata(CliCommand.CONTAINER_CONNECT))
131
+ def container_connect(
132
+ container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"),),
133
+ username: Optional[str] = typer.Option(
134
+ None,
135
+ '--username',
136
+ '-u',
137
+ help=__("Username for the server instance (required when connecting to self-hosted instance)"),
138
+ is_eager=False,
139
+ ),
140
+ private_ssh_key_path: str = typer.Option(
141
+ None,
142
+ "--private-key-path",
143
+ "-pk",
144
+ help=__("Path to private key that will be accepted by remote server (optional)"),
145
+ is_eager=False,
146
+ ),
147
+ ):
148
+ command_name = CliCommand.CONTAINER_CONNECT
149
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
150
+ check_command_permission(command_name)
151
+
152
+ if not container_uid:
153
+ typer.echo(__('Container unique ID is required'))
154
+ raise typer.Exit(1)
155
+
156
+ if private_ssh_key_path and not Path(private_ssh_key_path).is_file():
157
+ typer.echo(f'No file found at provided path {private_ssh_key_path}')
158
+ raise typer.Exit(1)
159
+
160
+ service_factory = validate_config_and_get_service_factory()
161
+ container_service: ContainerService = service_factory.get_container_service()
162
+
163
+ container_service.connect_to_container(
164
+ container_uid=container_uid,
165
+ username=username,
166
+ input_ssh_key_path=private_ssh_key_path,
167
+ )
168
+
169
+ app_logger.info(f'Stop connect to container')
170
+ raise typer.Exit(0)
171
+
172
+
173
+ @app.command(name="upload", no_args_is_help=True, help=__("Upload file to container"), **get_command_metadata(CliCommand.CONTAINER_UPLOAD))
174
+ def upload_file(
175
+ source_path: str = typer.Argument(help=__("Source file path"),),
176
+ destination: Optional[str] = typer.Argument(help=__("Destination directory path in container. Format: container_uid:/path/to/file"),),
177
+ username: Optional[str] = typer.Option(
178
+ None,
179
+ '--username',
180
+ '-u',
181
+ help=__("Username for the server instance (required when connecting to self-hosted instance)"),
182
+ is_eager=False,
183
+ ),
184
+ ):
185
+ command_name = CliCommand.CONTAINER_UPLOAD
186
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
187
+ check_command_permission(command_name)
188
+
189
+ container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", destination)
190
+
191
+ if container_args is None:
192
+ typer.echo(__('Container unique ID and source file path are required as the second argument'))
193
+ typer.echo(__('Example: container_uid:/path/to/file'))
194
+ raise typer.Exit(1)
195
+ container_slug = container_args.groups()[0]
196
+ destination_path = container_args.groups()[1].rstrip("/")
197
+
198
+ if not container_slug:
199
+ typer.echo(__('Container unique ID is required'))
200
+ raise typer.Exit(1)
201
+
202
+ service_factory = validate_config_and_get_service_factory()
203
+ container_service: ContainerService = service_factory.get_container_service()
204
+
205
+ container: Optional[DockerContainerDto] = container_service.get_container(
206
+ container_slug=container_slug,
207
+ )
208
+
209
+ if container:
210
+ container_service.check_if_container_running(
211
+ container=container
212
+ )
213
+
214
+ typer.echo(__("Uploading file(s) to container '%container-slug%'", {'container-slug': container_slug}))
215
+
216
+ container_service.put_file_to_container(
217
+ container=container,
218
+ src_path=source_path,
219
+ destination_path=destination_path,
220
+ username_param=username,
221
+ copy_only_folder_contents=source_path.endswith("/")
222
+ )
223
+ else:
224
+ typer.echo(__("Container not found: %container_item%", {'container_item': container_slug}))
225
+
226
+ app_logger.info(f'End send files to container')
227
+ raise typer.Exit(0)
228
+
229
+
230
+ @app.command(name="download", no_args_is_help=True, help=__("Download file from container"), **get_command_metadata(CliCommand.CONTAINER_DOWNLOAD))
231
+ def download_file(
232
+ source_path: str = typer.Argument(help=__("Source file path in container. Format: container_uid:/path/to/file"),),
233
+ destination_path: str = typer.Argument(help=__("Destination directory path on local machine"),),
234
+ username: Optional[str] = typer.Option(
235
+ None,
236
+ '--username',
237
+ '-u',
238
+ help=__("Username for the server instance (required when connecting to self-hosted instance)"),
239
+ is_eager=False,
240
+ ),
241
+ ):
242
+ command_name = CliCommand.CONTAINER_DOWNLOAD
243
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
244
+ check_command_permission(command_name)
245
+
246
+ container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", source_path)
247
+
248
+ if container_args is None:
249
+ typer.echo(__('Container unique ID and source directory path are required as the first argument'))
250
+ typer.echo(__('Example: container-uid:/path/to/file'))
251
+ raise typer.Exit(1)
252
+ container_slug = container_args.groups()[0]
253
+ source_path = container_args.groups()[1]
254
+
255
+ if not container_slug:
256
+ typer.echo(__('Container unique ID is required'))
257
+ raise typer.Exit(1)
258
+
259
+ service_factory = validate_config_and_get_service_factory()
260
+ container_service: ContainerService = service_factory.get_container_service()
261
+
262
+ container: Optional[DockerContainerDto] = container_service.get_container(
263
+ container_slug=container_slug,
264
+ )
265
+
266
+ if container:
267
+ container_service.check_if_container_running(
268
+ container=container
269
+ )
270
+
271
+ typer.echo(__("Downloading files from container: '%container-slug%'", {'container-slug': container_slug}))
272
+
273
+ container_service.get_file_from_container(
274
+ container=container,
275
+ src_path=source_path,
276
+ destination_path=destination_path.rstrip("/"),
277
+ username_param=username,
278
+ copy_only_folder_contents=source_path.endswith("/"),
279
+ )
280
+ else:
281
+ typer.echo(__("Container not found: %container_item%", {'container_item': container_slug}))
282
+
283
+ app_logger.info(f'End download files from container')
284
+ raise typer.Exit(0)
285
+
286
+
287
+ @app.command(name="start", no_args_is_help=True, help=__("Start container"), **get_command_metadata(CliCommand.CONTAINER_START))
288
+ def start_container(
289
+ container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"), ),
290
+ ):
291
+ command_name = CliCommand.CONTAINER_START
292
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
293
+ check_command_permission(command_name)
294
+
295
+ if not container_uid:
296
+ typer.echo(__('Container unique ID is required'))
297
+ raise typer.Exit(1)
298
+
299
+ service_factory = validate_config_and_get_service_factory()
300
+ container_service: ContainerService = service_factory.get_container_service()
301
+
302
+ container_service.request_docker_container_action(
303
+ container_uid=container_uid,
304
+ action=DockerContainerAction.START
305
+ )
306
+
307
+ app_logger.info(f'End start container')
308
+ raise typer.Exit(0)
309
+
310
+
311
+ @app.command(name="stop", no_args_is_help=True, help=__("Stop container"), **get_command_metadata(CliCommand.CONTAINER_STOP))
312
+ def stop_container(
313
+ container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"), ),
314
+ ):
315
+ command_name = CliCommand.CONTAINER_STOP
316
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
317
+ check_command_permission(command_name)
318
+
319
+ if not container_uid:
320
+ typer.echo(__('Container unique ID is required'))
321
+ raise typer.Exit(1)
322
+
323
+ service_factory = validate_config_and_get_service_factory()
324
+ container_service: ContainerService = service_factory.get_container_service()
325
+
326
+ container_service.request_docker_container_action(
327
+ container_uid=container_uid,
328
+ action=DockerContainerAction.STOP
329
+ )
330
+
331
+ app_logger.info(f'End stop container')
332
+ raise typer.Exit(0)
333
+
334
+
335
+ @app.command(name="restart", no_args_is_help=True, help=__("Restart container"), **get_command_metadata(CliCommand.CONTAINER_RESTART))
336
+ def restart_container(
337
+ container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"), ),
338
+ ):
339
+ command_name = CliCommand.CONTAINER_RESTART
340
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
341
+ check_command_permission(command_name)
342
+
343
+ if not container_uid:
344
+ typer.echo(__('Container unique ID is required'))
345
+ raise typer.Exit(1)
346
+
347
+ service_factory = validate_config_and_get_service_factory()
348
+ container_service: ContainerService = service_factory.get_container_service()
349
+
350
+ container_service.request_docker_container_action(
351
+ container_uid=container_uid,
352
+ action=DockerContainerAction.RESTART
353
+ )
354
+
355
+ app_logger.info(f'End stop container')
356
+ raise typer.Exit(0)
357
+
358
+
359
+ @app.command(name="logs", no_args_is_help=True, help=__("Stream real-time Docker container logs or view last logs for a container"), **get_command_metadata(CliCommand.CONTAINER_LOGS))
360
+ def container_logs(
361
+ container_uid: Optional[str] = typer.Argument(help=__("Container unique id")),
362
+ logs_number: Optional[int] = typer.Option(
363
+ None,
364
+ '--number',
365
+ '-n',
366
+ help=__("Display a number of latest log entries. No real-time stream if provided."),
367
+ is_eager=False,
368
+ ),
369
+ ):
370
+ command_name = CliCommand.CONTAINER_LOGS
371
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
372
+ check_command_permission(command_name)
373
+
374
+ if not container_uid:
375
+ typer.echo(__('Container unique ID is required'))
376
+ raise typer.Exit(1)
377
+
378
+ service_factory = validate_config_and_get_service_factory()
379
+ logging_service: LoggingService = service_factory.get_logging_service()
380
+
381
+ if logs_number is None:
382
+ logging_service.stream_container_logs_with_controls(
383
+ container_uid=container_uid
384
+ )
385
+ else:
386
+ logging_service.print_last_container_logs(container_uid=container_uid, logs_number=logs_number)
387
+
388
+ app_logger.info(f'Container logs - end')
389
+ raise typer.Exit(0)