thestage 0.5.46__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 -0
  2. thestage/__init__.py +3 -3
  3. thestage/__main__.py +9 -9
  4. thestage/cli_command.py +56 -0
  5. thestage/cli_command_helper.py +51 -0
  6. thestage/color_scheme/color_scheme.py +7 -7
  7. thestage/config/__init__.py +18 -18
  8. thestage/config/config_storage.py +5 -0
  9. thestage/config/env_base.py +7 -7
  10. thestage/controllers/__init__.py +0 -0
  11. thestage/controllers/base_controller.py +67 -63
  12. thestage/controllers/config_controller.py +137 -145
  13. thestage/controllers/container_controller.py +389 -425
  14. thestage/controllers/instance_controller.py +183 -200
  15. thestage/controllers/project_controller.py +802 -872
  16. thestage/controllers/utils_controller.py +32 -28
  17. thestage/debug_main.dist.py +28 -28
  18. thestage/entities/__init__.py +0 -0
  19. thestage/entities/container.py +17 -22
  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 -24
  30. thestage/entities/self_hosted_instance.py +18 -20
  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 -0
  43. thestage/helpers/logger/__init__.py +0 -0
  44. thestage/helpers/logger/app_logger.py +50 -51
  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 -24
  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 -127
  53. thestage/services/app_config_service.py +52 -51
  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 -734
  59. thestage/services/clients/thestage_api/core/api_client_core.py +108 -25
  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 -0
  138. thestage/services/config_provider/__init__.py +0 -0
  139. thestage/services/config_provider/config_provider.py +237 -209
  140. thestage/services/connect/connect_service.py +196 -207
  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 -391
  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 -20
  147. thestage/services/filesystem_service.py +133 -133
  148. thestage/services/instance/__init__.py +0 -0
  149. thestage/services/instance/instance_service.py +303 -317
  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 -395
  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 -1280
  168. thestage/services/remote_server_service.py +609 -610
  169. thestage/services/service_factory.py +97 -103
  170. thestage/services/task/dto/task_dto.py +40 -40
  171. thestage/services/validation_service.py +61 -56
  172. {thestage-0.5.46.dist-info → thestage-0.5.49.dist-info}/LICENSE.txt +12 -12
  173. {thestage-0.5.46.dist-info → thestage-0.5.49.dist-info}/METADATA +3 -4
  174. thestage-0.5.49.dist-info/RECORD +176 -0
  175. {thestage-0.5.46.dist-info → thestage-0.5.49.dist-info}/WHEEL +1 -1
  176. thestage/exceptions/http_error_exception.py +0 -12
  177. thestage/services/clients/thestage_api/core/api_client_abstract.py +0 -91
  178. thestage-0.5.46.dist-info/RECORD +0 -172
  179. {thestage-0.5.46.dist-info → thestage-0.5.49.dist-info}/entry_points.txt +0 -0
@@ -1,425 +1,389 @@
1
-
2
- import re
3
- from pathlib import Path
4
- from typing import Optional, List
5
-
6
- from thestage.services.clients.thestage_api.dtos.enums.container_pending_action import DockerContainerAction
7
- from thestage.services.clients.thestage_api.dtos.container_response import DockerContainerDto
8
- from thestage.i18n.translation import __
9
- from thestage.services.container.container_service import ContainerService
10
- from thestage.helpers.logger.app_logger import app_logger
11
- from thestage.controllers.utils_controller import validate_config_and_get_service_factory, get_current_directory
12
-
13
- import typer
14
-
15
- from thestage.services.logging.logging_service import LoggingService
16
-
17
- app = typer.Typer(no_args_is_help=True, help=__("Manage containers"))
18
-
19
-
20
- @app.command(name='ls', help=__("List containers"))
21
- def list_items(
22
- row: int = typer.Option(
23
- 5,
24
- '--row',
25
- '-r',
26
- help=__("Set number of rows displayed per page"),
27
- is_eager=False,
28
- ),
29
- page: int = typer.Option(
30
- 1,
31
- '--page',
32
- '-p',
33
- help=__("Set starting page for displaying output"),
34
- is_eager=False,
35
- ),
36
- project_uid: str = typer.Option(
37
- None,
38
- '--project-uid',
39
- '-puid',
40
- help=__("Filter containers by project unique ID"),
41
- is_eager=False,
42
- ),
43
- statuses: List[str] = typer.Option(
44
- None,
45
- '--status',
46
- '-s',
47
- help=__("Filter by status, use --status all to list all containers"),
48
- is_eager=False,
49
- ),
50
- ):
51
- """
52
- Lists containers
53
- """
54
- app_logger.info(f'Start container lists from {get_current_directory()}')
55
-
56
- service_factory = validate_config_and_get_service_factory()
57
- config = service_factory.get_config_provider().get_full_config()
58
-
59
- container_service: ContainerService = service_factory.get_container_service()
60
-
61
- container_service.print_container_list(
62
- config=config,
63
- row=row,
64
- page=page,
65
- project_uid=project_uid,
66
- statuses=statuses,
67
- )
68
-
69
- typer.echo(__("Containers listing complete"))
70
- raise typer.Exit(0)
71
-
72
-
73
- @app.command(name="info", no_args_is_help=True, help=__("Help get container details"))
74
- def item_details(
75
- container_uid: Optional[str] = typer.Argument(hidden=False, help=__("Container unique ID")),
76
- ):
77
- """
78
- Lists container details
79
- """
80
- app_logger.info(f'Start container details')
81
-
82
- if not container_uid:
83
- typer.echo(__('Container unique ID is required'))
84
- raise typer.Exit(1)
85
-
86
- service_factory = validate_config_and_get_service_factory()
87
- config = service_factory.get_config_provider().get_full_config()
88
-
89
- container_service: ContainerService = service_factory.get_container_service()
90
-
91
- container: Optional[DockerContainerDto] = container_service.get_container(
92
- config=config,
93
- container_slug=container_uid,
94
- )
95
-
96
- if not container:
97
- typer.echo(__("Container not found: %container_item%", {'container_item': str(container_uid) if container_uid else ''}))
98
- raise typer.Exit(1)
99
-
100
- typer.echo(__("STATUS: %status%", {'status': str(container.frontend_status.status_translation if container and container.frontend_status else 'UNKNOWN')}))
101
- typer.echo(__("UNIQUE ID: %slug%", {'slug': str(container.slug)}))
102
- typer.echo(__("TITLE: %title%", {'title': str(container.title)}))
103
- typer.echo(__("IMAGE: %image%", {'image': str(container.docker_image)}))
104
-
105
- if container.instance_rented:
106
- typer.echo(
107
- __("RENTED SERVER INSTANCE UNIQUE ID: %instance_slug%", {'instance_slug': str(container.instance_rented.slug)})
108
- )
109
- typer.echo(
110
- __("RENTED SERVER INSTANCE STATUS: %instance_status%",
111
- {'instance_status': str(container.instance_rented.frontend_status.status_translation if container.instance_rented.frontend_status else 'UNKNOWN')})
112
- )
113
-
114
- if container.selfhosted_instance:
115
- typer.echo(
116
- __("SELF-HOSTED INSTANCE UNIQUE ID: %instance_slug%", {'instance_slug': str(container.selfhosted_instance.slug)})
117
- )
118
- typer.echo(
119
- __("SELF-HOSTED INSTANCE STATUS: %instance_status%",
120
- {'instance_status': str(container.selfhosted_instance.frontend_status.status_translation if container.selfhosted_instance.frontend_status else 'UNKNOWN')})
121
- )
122
-
123
- if container.mappings and (container.mappings.port_mappings or container.mappings.directory_mappings):
124
- if container.mappings.port_mappings:
125
- typer.echo(__("CONTAINER PORT MAPPING:"))
126
- for src, dest in container.mappings.port_mappings.items():
127
- typer.echo(f" {src} : {dest}")
128
-
129
- if container.mappings.directory_mappings:
130
- typer.echo(__("CONTAINER DIRECTORY MAPPING:"))
131
- for src, dest in container.mappings.directory_mappings.items():
132
- typer.echo(f" {src} : {dest}")
133
-
134
- raise typer.Exit(0)
135
-
136
-
137
- @app.command(name="connect", no_args_is_help=True, help=__("Connect to container"))
138
- def container_connect(
139
- container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"),),
140
- username: Optional[str] = typer.Option(
141
- None,
142
- '--username',
143
- '-u',
144
- help=__("Username for the server instance (required when connecting to self-hosted instance)"),
145
- is_eager=False,
146
- ),
147
- private_ssh_key_path: str = typer.Option(
148
- None,
149
- "--private-key-path",
150
- "-pk",
151
- help=__("Path to private key that will be accepted by remote server (optional)"),
152
- is_eager=False,
153
- ),
154
- ):
155
- """
156
- Connects to container
157
- """
158
- app_logger.info(f'Connect to container')
159
-
160
- if not container_uid:
161
- typer.echo(__('Container unique ID is required'))
162
- raise typer.Exit(1)
163
-
164
- if private_ssh_key_path and not Path(private_ssh_key_path).is_file():
165
- typer.echo(f'No file found at provided path {private_ssh_key_path}')
166
- raise typer.Exit(1)
167
-
168
- service_factory = validate_config_and_get_service_factory()
169
- config = service_factory.get_config_provider().get_full_config()
170
-
171
- container_service: ContainerService = service_factory.get_container_service()
172
-
173
- container_service.connect_to_container(
174
- config=config,
175
- container_uid=container_uid,
176
- username=username,
177
- input_ssh_key_path=private_ssh_key_path,
178
- )
179
-
180
- app_logger.info(f'Stop connect to container')
181
- raise typer.Exit(0)
182
-
183
-
184
- @app.command(name="upload", no_args_is_help=True, help=__("Upload file to container"))
185
- def put_file(
186
- source_path: str = typer.Argument(help=__("Source file path"),),
187
- destination: Optional[str] = typer.Argument(help=__("Destination directory path in container. Format: container_uid:/path/to/file"),),
188
- username: Optional[str] = typer.Option(
189
- None,
190
- '--username',
191
- '-u',
192
- help=__("Username for the server instance (required when connecting to self-hosted instance)"),
193
- is_eager=False,
194
- ),
195
- ):
196
- """
197
- Uploads file to container
198
- """
199
- app_logger.info(f'Push file to container')
200
-
201
- container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", destination)
202
-
203
- if container_args is None:
204
- typer.echo(__('Container unique ID and source file path are required as the second argument'))
205
- typer.echo(__('Example: container_uid:/path/to/file'))
206
- raise typer.Exit(1)
207
- container_slug = container_args.groups()[0]
208
- destination_path = container_args.groups()[1].rstrip("/")
209
-
210
- if not container_slug:
211
- typer.echo(__('Container unique ID is required'))
212
- raise typer.Exit(1)
213
-
214
- service_factory = validate_config_and_get_service_factory()
215
- config = service_factory.get_config_provider().get_full_config()
216
-
217
- container_service: ContainerService = service_factory.get_container_service()
218
-
219
- container: Optional[DockerContainerDto] = container_service.get_container(
220
- config=config,
221
- container_slug=container_slug,
222
- )
223
-
224
- if container:
225
- container_service.check_if_container_running(
226
- container=container
227
- )
228
-
229
- typer.echo(__("Uploading file(s) to container '%container-slug%'", {'container-slug': container_slug}))
230
-
231
- container_service.put_file_to_container(
232
- container=container,
233
- src_path=source_path,
234
- destination_path=destination_path,
235
- username_param=username,
236
- copy_only_folder_contents=source_path.endswith("/")
237
- )
238
- else:
239
- typer.echo(__("Container not found: %container_item%", {'container_item': container_slug}))
240
-
241
- app_logger.info(f'End send files to container')
242
- raise typer.Exit(0)
243
-
244
-
245
- @app.command(name="download", no_args_is_help=True, help=__("Download file from container"))
246
- def download_file(
247
- source_path: str = typer.Argument(help=__("Source file path in container. Format: container_uid:/path/to/file"),),
248
- destination_path: str = typer.Argument(help=__("Destination directory path on local machine"),),
249
- username: Optional[str] = typer.Option(
250
- None,
251
- '--username',
252
- '-u',
253
- help=__("Username for the server instance (required when connecting to self-hosted instance)"),
254
- is_eager=False,
255
- ),
256
- ):
257
- """
258
- Downloads file from container
259
- """
260
- app_logger.info(f'Download file from container')
261
-
262
- container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", source_path)
263
-
264
- if container_args is None:
265
- typer.echo(__('Container unique ID and source directory path are required as the first argument'))
266
- typer.echo(__('Example: container-uid:/path/to/file'))
267
- raise typer.Exit(1)
268
- container_slug = container_args.groups()[0]
269
- source_path = container_args.groups()[1]
270
-
271
- if not container_slug:
272
- typer.echo(__('Container unique ID is required'))
273
- raise typer.Exit(1)
274
-
275
- service_factory = validate_config_and_get_service_factory()
276
- config = service_factory.get_config_provider().get_full_config()
277
-
278
- container_service: ContainerService = service_factory.get_container_service()
279
-
280
- container: Optional[DockerContainerDto] = container_service.get_container(
281
- config=config,
282
- container_slug=container_slug,
283
- )
284
-
285
- if container:
286
- container_service.check_if_container_running(
287
- container=container
288
- )
289
-
290
- typer.echo(__("Downloading files from container: '%container-slug%'", {'container-slug': container_slug}))
291
-
292
- container_service.get_file_from_container(
293
- container=container,
294
- src_path=source_path,
295
- destination_path=destination_path.rstrip("/"),
296
- username_param=username,
297
- copy_only_folder_contents=source_path.endswith("/"),
298
- config=config
299
- )
300
- else:
301
- typer.echo(__("Container not found: %container_item%", {'container_item': container_slug}))
302
-
303
- app_logger.info(f'End download files from container')
304
- raise typer.Exit(0)
305
-
306
-
307
- @app.command(name="start", no_args_is_help=True, help=__("Start container"))
308
- def start_container(
309
- container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"), ),
310
- ):
311
- """
312
- Starts container
313
- """
314
- app_logger.info(f'Start container')
315
-
316
- if not container_uid:
317
- typer.echo(__('Container unique ID is required'))
318
- raise typer.Exit(1)
319
-
320
- service_factory = validate_config_and_get_service_factory()
321
- config = service_factory.get_config_provider().get_full_config()
322
-
323
- container_service: ContainerService = service_factory.get_container_service()
324
-
325
- container_service.request_docker_container_action(
326
- config=config,
327
- container_uid=container_uid,
328
- action=DockerContainerAction.START
329
- )
330
-
331
- app_logger.info(f'End start container')
332
- raise typer.Exit(0)
333
-
334
-
335
- @app.command(name="stop", no_args_is_help=True, help=__("Stop container"))
336
- def stop_container(
337
- container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"), ),
338
- ):
339
- """
340
- Stops container
341
- """
342
- app_logger.info(f'Stop container')
343
-
344
- if not container_uid:
345
- typer.echo(__('Container unique ID is required'))
346
- raise typer.Exit(1)
347
-
348
- service_factory = validate_config_and_get_service_factory()
349
- config = service_factory.get_config_provider().get_full_config()
350
-
351
- container_service: ContainerService = service_factory.get_container_service()
352
-
353
- container_service.request_docker_container_action(
354
- config=config,
355
- container_uid=container_uid,
356
- action=DockerContainerAction.STOP
357
- )
358
-
359
- app_logger.info(f'End stop container')
360
- raise typer.Exit(0)
361
-
362
-
363
- @app.command(name="restart", no_args_is_help=True, help=__("Restart container"))
364
- def stop_container(
365
- container_uid: Optional[str] = typer.Argument(help=__("Container unique ID"), ),
366
- ):
367
- """
368
- Stops container
369
- """
370
- app_logger.info(f'Restart container')
371
-
372
- if not container_uid:
373
- typer.echo(__('Container unique ID is required'))
374
- raise typer.Exit(1)
375
-
376
- service_factory = validate_config_and_get_service_factory()
377
- config = service_factory.get_config_provider().get_full_config()
378
-
379
- container_service: ContainerService = service_factory.get_container_service()
380
-
381
- container_service.request_docker_container_action(
382
- config=config,
383
- container_uid=container_uid,
384
- action=DockerContainerAction.RESTART
385
- )
386
-
387
- app_logger.info(f'End stop container')
388
- raise typer.Exit(0)
389
-
390
-
391
- @app.command(name="logs", no_args_is_help=True, help=__("Stream real-time Docker container logs or view last logs for a container"))
392
- def container_logs(
393
- container_uid: Optional[str] = typer.Argument(help=__("Container unique id")),
394
- logs_number: Optional[int] = typer.Option(
395
- None,
396
- '--number',
397
- '-n',
398
- help=__("Display a number of latest log entries. No real-time stream if provided."),
399
- is_eager=False,
400
- ),
401
- ):
402
- """
403
- Streams real-time container logs
404
- """
405
- app_logger.info(f'View container logs')
406
-
407
- if not container_uid:
408
- typer.echo(__('Container unique ID is required'))
409
- raise typer.Exit(1)
410
-
411
- service_factory = validate_config_and_get_service_factory()
412
- config = service_factory.get_config_provider().get_full_config()
413
-
414
- logging_service: LoggingService = service_factory.get_logging_service()
415
-
416
- if logs_number is None:
417
- logging_service.stream_container_logs_with_controls(
418
- config=config,
419
- container_uid=container_uid
420
- )
421
- else:
422
- logging_service.print_last_container_logs(config=config, container_uid=container_uid, logs_number=logs_number)
423
-
424
- app_logger.info(f'Container logs - end')
425
- 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)