thestage 0.5.43__py3-none-any.whl → 0.5.45__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- thestage/__init__.py +1 -1
- thestage/color_scheme/color_scheme.py +2 -1
- thestage/controllers/container_controller.py +0 -3
- thestage/controllers/instance_controller.py +8 -75
- thestage/controllers/project_controller.py +92 -128
- thestage/main.py +6 -1
- thestage/services/clients/git/git_client.py +42 -24
- thestage/services/clients/thestage_api/api_client.py +16 -38
- thestage/services/clients/thestage_api/dtos/{user_profile.py → user_controller/user_profile.py} +6 -3
- thestage/services/config_provider/config_provider.py +10 -29
- thestage/services/connect/connect_service.py +12 -5
- thestage/services/container/container_service.py +2 -2
- thestage/services/core_files/config_entity.py +2 -7
- thestage/services/filesystem_service.py +20 -2
- thestage/services/instance/instance_service.py +100 -6
- thestage/services/logging/logging_service.py +1 -1
- thestage/services/project/project_service.py +202 -8
- thestage/services/remote_server_service.py +2 -2
- thestage/services/service_factory.py +4 -4
- thestage/services/validation_service.py +0 -6
- {thestage-0.5.43.dist-info → thestage-0.5.45.dist-info}/METADATA +2 -2
- {thestage-0.5.43.dist-info → thestage-0.5.45.dist-info}/RECORD +25 -25
- {thestage-0.5.43.dist-info → thestage-0.5.45.dist-info}/LICENSE.txt +0 -0
- {thestage-0.5.43.dist-info → thestage-0.5.45.dist-info}/WHEEL +0 -0
- {thestage-0.5.43.dist-info → thestage-0.5.45.dist-info}/entry_points.txt +0 -0
|
@@ -85,12 +85,11 @@ from thestage.services.clients.thestage_api.dtos.task_controller.task_list_for_p
|
|
|
85
85
|
from thestage.services.clients.thestage_api.dtos.task_controller.task_status_localized_map_response import \
|
|
86
86
|
TaskStatusLocalizedMapResponse
|
|
87
87
|
from thestage.services.clients.thestage_api.dtos.task_controller.task_view_response import TaskViewResponse
|
|
88
|
-
from thestage.services.clients.thestage_api.dtos.user_profile import UserProfileResponse
|
|
89
88
|
from thestage.services.clients.thestage_api.dtos.instance_rented_response import InstanceRentedListResponse, \
|
|
90
89
|
InstanceRentedDto, InstanceRentedItemResponse, InstanceRentedBusinessStatusMapperResponse
|
|
91
90
|
|
|
92
|
-
from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
|
|
93
|
-
|
|
91
|
+
from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
|
|
92
|
+
from thestage.services.clients.thestage_api.dtos.user_controller.user_profile import UserProfileResponse
|
|
94
93
|
from thestage.services.project.dto.inference_simulator_dto import InferenceSimulatorDto
|
|
95
94
|
from thestage.services.project.dto.inference_simulator_model_dto import InferenceSimulatorModelDto
|
|
96
95
|
from thestage.services.task.dto.task_dto import TaskDto
|
|
@@ -146,7 +145,6 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
146
145
|
result = TaskListForProjectResponse.model_validate(response) if response else None
|
|
147
146
|
return result.tasks if result and result.is_success else None
|
|
148
147
|
|
|
149
|
-
|
|
150
148
|
def get_inference_simulator_list_for_project(
|
|
151
149
|
self,
|
|
152
150
|
token: str,
|
|
@@ -203,7 +201,6 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
203
201
|
result = InferenceSimulatorModelListForProjectResponse.model_validate(response) if response else None
|
|
204
202
|
return result.inferenceSimulatorModels if result and result.is_success else None
|
|
205
203
|
|
|
206
|
-
|
|
207
204
|
def get_rented_instance_list(
|
|
208
205
|
self,
|
|
209
206
|
token: str,
|
|
@@ -260,7 +257,7 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
260
257
|
|
|
261
258
|
return data.taskStatusMap if data else None
|
|
262
259
|
|
|
263
|
-
def
|
|
260
|
+
def get_rented_instance(
|
|
264
261
|
self,
|
|
265
262
|
token: str,
|
|
266
263
|
instance_slug: Optional[str] = None,
|
|
@@ -282,7 +279,7 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
282
279
|
|
|
283
280
|
return InstanceRentedItemResponse.model_validate(response).instance_rented if response else None
|
|
284
281
|
|
|
285
|
-
def
|
|
282
|
+
def get_selfhosted_instance(
|
|
286
283
|
self,
|
|
287
284
|
token: str,
|
|
288
285
|
instance_slug: Optional[str] = None,
|
|
@@ -347,21 +344,8 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
347
344
|
data = SelfHostedRentedRentedBusinessStatusMapperResponse.model_validate(response) if response else None
|
|
348
345
|
return data.selfhosted_instance_business_status_map if data else None
|
|
349
346
|
|
|
350
|
-
def get_profile(
|
|
351
|
-
self,
|
|
352
|
-
token: str,
|
|
353
|
-
) -> Optional[UserProfileResponse]:
|
|
354
347
|
|
|
355
|
-
|
|
356
|
-
method='GET',
|
|
357
|
-
url='/frontend-api/user/my-profile',
|
|
358
|
-
token=token,
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
result = UserProfileResponse.model_validate(response) if response else None
|
|
362
|
-
return result if result else None
|
|
363
|
-
|
|
364
|
-
def stop_task_on_instance(
|
|
348
|
+
def cancel_task(
|
|
365
349
|
self,
|
|
366
350
|
token: str,
|
|
367
351
|
task_id: int,
|
|
@@ -521,8 +505,9 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
521
505
|
|
|
522
506
|
return ProjectRunTaskResponse.model_validate(response) if response else None
|
|
523
507
|
|
|
524
|
-
|
|
525
|
-
|
|
508
|
+
async def poll_logs_httpx(self, token: str, docker_container_id: Optional[int], last_log_timestamp: str,
|
|
509
|
+
last_log_id: str, task_id: Optional[int] = None,
|
|
510
|
+
inference_simulator_id: Optional[int] = None) -> Optional[LogPollingResponse]:
|
|
526
511
|
request_headers = {'Content-Type': 'application/json'}
|
|
527
512
|
if token: request_headers['Authorization'] = f"Bearer {token}"
|
|
528
513
|
|
|
@@ -544,7 +529,6 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
544
529
|
|
|
545
530
|
return LogPollingResponse.model_validate(response.json()) if response else None
|
|
546
531
|
|
|
547
|
-
|
|
548
532
|
def add_public_ssh_key_to_user(self, token: str, public_key: str, note: str) -> AddSshKeyToUserResponse:
|
|
549
533
|
request = AddSshKeyToUserRequest(
|
|
550
534
|
sshKey=public_key,
|
|
@@ -561,7 +545,6 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
561
545
|
result = AddSshKeyToUserResponse.model_validate(response) if response else None
|
|
562
546
|
return result
|
|
563
547
|
|
|
564
|
-
|
|
565
548
|
def is_user_has_ssh_public_key(self, token: str, public_key: str) -> IsUserHasSshPublicKeyResponse:
|
|
566
549
|
request = IsUserHasSshPublicKeyRequest(
|
|
567
550
|
sshKey=public_key,
|
|
@@ -577,8 +560,8 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
577
560
|
result = IsUserHasSshPublicKeyResponse.model_validate(response) if response else None
|
|
578
561
|
return result
|
|
579
562
|
|
|
580
|
-
|
|
581
|
-
|
|
563
|
+
def add_public_ssh_key_to_instance_rented(self, token: str, instance_rented_id: int,
|
|
564
|
+
ssh_key_pair_id: int) -> AddSshPublicKeyToInstanceResponse:
|
|
582
565
|
request = AddSshPublicKeyToInstanceRequest(
|
|
583
566
|
instanceRentedId=instance_rented_id,
|
|
584
567
|
sshPublicKeyId=ssh_key_pair_id,
|
|
@@ -594,7 +577,6 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
594
577
|
result = AddSshPublicKeyToInstanceResponse.model_validate(response) if response else None
|
|
595
578
|
return result
|
|
596
579
|
|
|
597
|
-
|
|
598
580
|
def start_project_inference_simulator(
|
|
599
581
|
self,
|
|
600
582
|
token: str,
|
|
@@ -625,7 +607,6 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
625
607
|
|
|
626
608
|
return ProjectStartInferenceSimulatorResponse.model_validate(response) if response else None
|
|
627
609
|
|
|
628
|
-
|
|
629
610
|
def push_project_inference_simulator_model(
|
|
630
611
|
self,
|
|
631
612
|
token: str,
|
|
@@ -644,7 +625,6 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
644
625
|
|
|
645
626
|
return ProjectPushInferenceSimulatorModelResponse.model_validate(response) if response else None
|
|
646
627
|
|
|
647
|
-
|
|
648
628
|
def get_inference_simulator_business_status_map(self, token: str, ) -> Optional[Dict[str, str]]:
|
|
649
629
|
response = self._request(
|
|
650
630
|
method='POST',
|
|
@@ -657,7 +637,6 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
657
637
|
|
|
658
638
|
return data.inference_simulator_status_map if data else None
|
|
659
639
|
|
|
660
|
-
|
|
661
640
|
def get_inference_simulator_model_business_status_map(self, token: str, ) -> Optional[Dict[str, str]]:
|
|
662
641
|
response = self._request(
|
|
663
642
|
method='POST',
|
|
@@ -688,7 +667,6 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
688
667
|
)
|
|
689
668
|
return GetInferenceSimulatorResponse.model_validate(response) if response else None
|
|
690
669
|
|
|
691
|
-
|
|
692
670
|
@error_handler()
|
|
693
671
|
def deploy_inference_model_to_instance(
|
|
694
672
|
self,
|
|
@@ -702,8 +680,8 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
702
680
|
request = DeployInferenceModelToInstanceRequest(
|
|
703
681
|
inferenceSimulatorSlug=unique_id_with_timestamp,
|
|
704
682
|
modelSlug=unique_id,
|
|
705
|
-
instanceRentedSlug
|
|
706
|
-
selfhostedInstanceSlug
|
|
683
|
+
instanceRentedSlug=rented_instance_unique_id,
|
|
684
|
+
selfhostedInstanceSlug=self_hosted_instance_unique_id
|
|
707
685
|
)
|
|
708
686
|
|
|
709
687
|
response = self._request(
|
|
@@ -714,7 +692,6 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
714
692
|
)
|
|
715
693
|
return DeployInferenceModelToInstanceResponse.model_validate(response) if response else None
|
|
716
694
|
|
|
717
|
-
|
|
718
695
|
@error_handler()
|
|
719
696
|
def deploy_inference_model_to_sagemaker(
|
|
720
697
|
self,
|
|
@@ -735,8 +712,9 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
735
712
|
)
|
|
736
713
|
return DeployInferenceModelToSagemakerResponse.model_validate(response) if response else None
|
|
737
714
|
|
|
738
|
-
|
|
739
|
-
|
|
715
|
+
def query_user_logs(self, token: str, limit: int, task_id: Optional[int] = None,
|
|
716
|
+
inference_simulator_id: Optional[int] = None,
|
|
717
|
+
container_id: Optional[int] = None) -> UserLogsQueryResponse:
|
|
740
718
|
request = UserLogsQueryRequest(
|
|
741
719
|
inferenceSimulatorId=inference_simulator_id,
|
|
742
720
|
taskId=task_id,
|
|
@@ -753,4 +731,4 @@ class TheStageApiClient(TheStageApiClientCore):
|
|
|
753
731
|
)
|
|
754
732
|
|
|
755
733
|
result = UserLogsQueryResponse.model_validate(response) if response else None
|
|
756
|
-
return result
|
|
734
|
+
return result
|
thestage/services/clients/thestage_api/dtos/{user_profile.py → user_controller/user_profile.py}
RENAMED
|
@@ -3,7 +3,10 @@ from typing import Optional
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class
|
|
7
|
-
name: Optional[str] = Field(None, alias='name')
|
|
6
|
+
class UserProfile(BaseModel):
|
|
8
7
|
email: Optional[str] = Field(None, alias='email')
|
|
9
|
-
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UserProfileResponse(BaseModel):
|
|
12
|
+
userProfile: Optional[UserProfile] = Field(None, alias='userProfile')
|
|
@@ -10,7 +10,7 @@ from thestage.exceptions.file_system_exception import FileSystemException
|
|
|
10
10
|
from thestage.services.core_files.config_entity import ConfigEntity
|
|
11
11
|
from thestage.helpers.ssh_util import parse_private_key
|
|
12
12
|
from thestage.services.connect.dto.remote_server_config import RemoteServerConfig
|
|
13
|
-
from thestage.services.filesystem_service import
|
|
13
|
+
from thestage.services.filesystem_service import FileSystemService
|
|
14
14
|
from thestage.services.project.dto.project_config import ProjectConfig
|
|
15
15
|
from thestage.config import THESTAGE_CONFIG_DIR, THESTAGE_CONFIG_FILE, THESTAGE_AUTH_TOKEN, THESTAGE_API_URL
|
|
16
16
|
|
|
@@ -20,30 +20,27 @@ class ConfigProvider():
|
|
|
20
20
|
_local_config_path: Optional[Path] = None
|
|
21
21
|
_global_config_path: Optional[Path] = None
|
|
22
22
|
_global_config_file: Optional[Path] = None
|
|
23
|
-
_file_system_service =
|
|
23
|
+
_file_system_service = FileSystemService
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def __init__(
|
|
27
27
|
self,
|
|
28
28
|
local_path: Optional[str] = None,
|
|
29
29
|
):
|
|
30
|
-
self._file_system_service =
|
|
30
|
+
self._file_system_service = FileSystemService()
|
|
31
31
|
if local_path:
|
|
32
32
|
self._local_path = self._file_system_service.get_path(directory=local_path, auto_create=False)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
self._local_config_path = self._local_path.joinpath(config_folder_name)
|
|
34
|
+
self._local_config_path = self._local_path.joinpath(THESTAGE_CONFIG_DIR)
|
|
36
35
|
|
|
37
36
|
home_dir = self._file_system_service.get_home_path()
|
|
38
|
-
self._global_config_path = home_dir.joinpath(
|
|
37
|
+
self._global_config_path = home_dir.joinpath(THESTAGE_CONFIG_DIR)
|
|
39
38
|
|
|
40
39
|
if self._global_config_path:
|
|
41
40
|
if not self._global_config_path.exists():
|
|
42
41
|
self._file_system_service.create_if_not_exists_dir(self._global_config_path)
|
|
43
42
|
|
|
44
|
-
self._global_config_file = self._global_config_path.joinpath(
|
|
45
|
-
self._file_system_service.get_path(f"{THESTAGE_CONFIG_FILE}", False)
|
|
46
|
-
)
|
|
43
|
+
self._global_config_file = self._global_config_path.joinpath(THESTAGE_CONFIG_FILE)
|
|
47
44
|
if not self._global_config_file.exists():
|
|
48
45
|
self._file_system_service.create_if_not_exists_file(self._global_config_file)
|
|
49
46
|
|
|
@@ -61,7 +58,7 @@ class ConfigProvider():
|
|
|
61
58
|
if config_from_env:
|
|
62
59
|
self.__update_config_values_dict(values_to_update=config_values, new_values=config_from_env)
|
|
63
60
|
|
|
64
|
-
config_from_file = self.
|
|
61
|
+
config_from_file = self._file_system_service.read_config_file(self._global_config_file)
|
|
65
62
|
if config_from_file:
|
|
66
63
|
self.__update_config_values_dict(values_to_update=config_values, new_values=config_from_file)
|
|
67
64
|
|
|
@@ -112,7 +109,7 @@ class ConfigProvider():
|
|
|
112
109
|
if not project_data_filepath.exists():
|
|
113
110
|
return None
|
|
114
111
|
|
|
115
|
-
config_data = self.
|
|
112
|
+
config_data = self._file_system_service.read_config_file(project_data_filepath) if project_data_filepath and project_data_filepath.exists() else {}
|
|
116
113
|
return ProjectConfig.model_validate(config_data)
|
|
117
114
|
|
|
118
115
|
|
|
@@ -165,7 +162,7 @@ class ConfigProvider():
|
|
|
165
162
|
if not config_filepath.is_file():
|
|
166
163
|
return None
|
|
167
164
|
|
|
168
|
-
config_data = self.
|
|
165
|
+
config_data = self._file_system_service.read_config_file(config_filepath) if config_filepath and config_filepath.exists() else {}
|
|
169
166
|
return RemoteServerConfig.model_validate(config_data)
|
|
170
167
|
|
|
171
168
|
|
|
@@ -188,22 +185,6 @@ class ConfigProvider():
|
|
|
188
185
|
else:
|
|
189
186
|
values_to_update['main'] = new_values['main']
|
|
190
187
|
|
|
191
|
-
|
|
192
|
-
def __read_config_file(self, path: Path) -> Dict[str, Any]:
|
|
193
|
-
result = {}
|
|
194
|
-
try:
|
|
195
|
-
if path and path.exists():
|
|
196
|
-
with path.open("r") as file:
|
|
197
|
-
try:
|
|
198
|
-
if os.stat(path).st_size != 0:
|
|
199
|
-
result = json.load(file)
|
|
200
|
-
except JSONDecodeError:
|
|
201
|
-
pass
|
|
202
|
-
except OSError:
|
|
203
|
-
raise FileSystemException(f"Could not open config file: {path}")
|
|
204
|
-
return result
|
|
205
|
-
|
|
206
|
-
|
|
207
188
|
@staticmethod
|
|
208
189
|
def __save_config_file(data: Dict, file_path: Path):
|
|
209
190
|
with open(file_path, 'w') as configfile:
|
|
@@ -211,7 +192,7 @@ class ConfigProvider():
|
|
|
211
192
|
|
|
212
193
|
|
|
213
194
|
def save_config(self, config: ConfigEntity):
|
|
214
|
-
data: Dict[str, Any] = self.
|
|
195
|
+
data: Dict[str, Any] = self._file_system_service.read_config_file(self._global_config_file)
|
|
215
196
|
data.update(config.model_dump(exclude_none=True, by_alias=True, exclude={'runtime', 'RUNTIME', 'daemon', 'DAEMON'}))
|
|
216
197
|
self.__save_config_file(data=data, file_path=self._global_config_file)
|
|
217
198
|
|
|
@@ -50,20 +50,25 @@ class ConnectService(AbstractService):
|
|
|
50
50
|
):
|
|
51
51
|
config = self._config_provider.get_full_config()
|
|
52
52
|
|
|
53
|
-
# TODO make a single separate method that will return all needed info
|
|
54
53
|
try:
|
|
55
|
-
instance_selfhosted = self.__thestage_api_client.
|
|
54
|
+
instance_selfhosted = self.__thestage_api_client.get_selfhosted_instance(token=config.main.thestage_auth_token, instance_slug=uid)
|
|
56
55
|
except HttpClientException as e:
|
|
56
|
+
if e.get_status_code() == 403:
|
|
57
|
+
typer.echo("Missing permission to view self-hosted instances")
|
|
57
58
|
instance_selfhosted = None
|
|
58
59
|
|
|
59
60
|
try:
|
|
60
|
-
instance_rented = self.__thestage_api_client.
|
|
61
|
+
instance_rented = self.__thestage_api_client.get_rented_instance(token=config.main.thestage_auth_token, instance_slug=uid)
|
|
61
62
|
except HttpClientException as e:
|
|
63
|
+
if e.get_status_code() == 403:
|
|
64
|
+
typer.echo("Missing permission to view rented instances")
|
|
62
65
|
instance_rented = None
|
|
63
66
|
|
|
64
67
|
try:
|
|
65
68
|
container = self.__thestage_api_client.get_container(token=config.main.thestage_auth_token, container_slug=uid, )
|
|
66
|
-
except
|
|
69
|
+
except HttpClientException as e:
|
|
70
|
+
if e.get_status_code() == 403:
|
|
71
|
+
typer.echo("Missing permission to view containers")
|
|
67
72
|
container = None
|
|
68
73
|
|
|
69
74
|
task: Optional[TaskDto] = None
|
|
@@ -71,6 +76,8 @@ class ConnectService(AbstractService):
|
|
|
71
76
|
try:
|
|
72
77
|
task_view_response = self.__thestage_api_client.get_task(token=config.main.thestage_auth_token, task_id=int(uid))
|
|
73
78
|
except HttpClientException as e:
|
|
79
|
+
if e.get_status_code() == 403:
|
|
80
|
+
typer.echo("Missing permission to view tasks")
|
|
74
81
|
task_view_response = None
|
|
75
82
|
if task_view_response and task_view_response.task:
|
|
76
83
|
task = task_view_response.task
|
|
@@ -148,7 +155,7 @@ class ConnectService(AbstractService):
|
|
|
148
155
|
instance_rented: Optional[InstanceRentedDto] = None
|
|
149
156
|
if instance_slug:
|
|
150
157
|
try:
|
|
151
|
-
instance_rented = self.__thestage_api_client.
|
|
158
|
+
instance_rented = self.__thestage_api_client.get_rented_instance(token=config.main.thestage_auth_token, instance_slug=instance_slug)
|
|
152
159
|
except HttpClientException as e:
|
|
153
160
|
instance_rented = None
|
|
154
161
|
|
|
@@ -10,7 +10,7 @@ from thestage.services.clients.thestage_api.dtos.enums.container_status import D
|
|
|
10
10
|
from thestage.entities.enums.shell_type import ShellType
|
|
11
11
|
from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
|
|
12
12
|
from thestage.services.container.mapper.container_mapper import ContainerMapper
|
|
13
|
-
from thestage.services.filesystem_service import
|
|
13
|
+
from thestage.services.filesystem_service import FileSystemService
|
|
14
14
|
from thestage.services.remote_server_service import RemoteServerService
|
|
15
15
|
from thestage.i18n.translation import __
|
|
16
16
|
from thestage.services.abstract_service import AbstractService
|
|
@@ -29,7 +29,7 @@ class ContainerService(AbstractService):
|
|
|
29
29
|
thestage_api_client: TheStageApiClient,
|
|
30
30
|
config_provider: ConfigProvider,
|
|
31
31
|
remote_server_service: RemoteServerService,
|
|
32
|
-
file_system_service:
|
|
32
|
+
file_system_service: FileSystemService,
|
|
33
33
|
):
|
|
34
34
|
super(ContainerService, self).__init__(
|
|
35
35
|
config_provider=config_provider
|
|
@@ -7,11 +7,6 @@ class MainConfigEntity(BaseModel):
|
|
|
7
7
|
thestage_auth_token: Optional[str] = Field(None, alias='thestage_auth_token')
|
|
8
8
|
thestage_api_url: Optional[str] = Field(None, alias='thestage_api_url')
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
class DaemonConfigEntity(BaseModel):
|
|
12
|
-
daemon_token: Optional[str] = Field(None, alias='daemon_token')
|
|
13
|
-
backend_api_url: Optional[str] = Field(None, alias='backend_api_url')
|
|
14
|
-
|
|
15
10
|
# not saved to file
|
|
16
11
|
class RuntimeConfigEntity(BaseModel):
|
|
17
12
|
working_directory: Optional[str] = Field(None, alias='working_directory')
|
|
@@ -19,7 +14,7 @@ class RuntimeConfigEntity(BaseModel):
|
|
|
19
14
|
|
|
20
15
|
|
|
21
16
|
class ConfigEntity(BaseModel):
|
|
17
|
+
global_config_path: str = Field(None, alias='global_config_path')
|
|
18
|
+
can_use_inference: bool = Field(None, alias='can_use_inference')
|
|
22
19
|
main: MainConfigEntity = Field(default_factory=MainConfigEntity, alias='main')
|
|
23
20
|
runtime: RuntimeConfigEntity = Field(default_factory=RuntimeConfigEntity, alias="runtime") # TODO merge with main
|
|
24
|
-
daemon: DaemonConfigEntity = Field(default_factory=DaemonConfigEntity, alias="daemon") # TODO this should not be in core package
|
|
25
|
-
start_on_daemon: bool = Field(False, alias='start_on_daemon') # TODO this should not be in core package
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import os
|
|
2
3
|
import shutil
|
|
4
|
+
from json import JSONDecodeError
|
|
3
5
|
from pathlib import Path
|
|
4
|
-
from typing import Optional, List
|
|
6
|
+
from typing import Optional, List, Dict, Any
|
|
5
7
|
|
|
6
8
|
from thestage.entities.file_item import FileItemEntity
|
|
7
9
|
from thestage.exceptions.file_system_exception import FileSystemException
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
class
|
|
12
|
+
class FileSystemService:
|
|
11
13
|
|
|
12
14
|
def get_ssh_path(self) -> Optional[Path]:
|
|
13
15
|
home_path = self.get_home_path()
|
|
@@ -83,6 +85,7 @@ class FileSystemServiceCore:
|
|
|
83
85
|
file.write(new_line)
|
|
84
86
|
file.write('\n')
|
|
85
87
|
|
|
88
|
+
# TODO remove this fucking useless shit
|
|
86
89
|
def check_if_path_exist(self, file: str) -> bool:
|
|
87
90
|
path = self.get_path(file, auto_create=False)
|
|
88
91
|
if path.exists():
|
|
@@ -113,3 +116,18 @@ class FileSystemServiceCore:
|
|
|
113
116
|
real_path = self.get_path(directory=path, auto_create=False)
|
|
114
117
|
if real_path and real_path.exists():
|
|
115
118
|
shutil.rmtree(real_path)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def read_config_file(self, path: Path) -> Dict[str, Any]:
|
|
122
|
+
result = {}
|
|
123
|
+
try:
|
|
124
|
+
if path and path.exists():
|
|
125
|
+
with path.open("r") as file:
|
|
126
|
+
try:
|
|
127
|
+
if os.stat(path).st_size != 0:
|
|
128
|
+
result = json.load(file)
|
|
129
|
+
except JSONDecodeError:
|
|
130
|
+
pass
|
|
131
|
+
except OSError:
|
|
132
|
+
raise FileSystemException(f"Could not open config file: {path}")
|
|
133
|
+
return result
|
|
@@ -2,6 +2,9 @@ from pathlib import Path
|
|
|
2
2
|
from typing import List, Optional, Dict
|
|
3
3
|
|
|
4
4
|
import typer
|
|
5
|
+
|
|
6
|
+
from thestage.entities.rented_instance import RentedInstanceEntity
|
|
7
|
+
from thestage.entities.self_hosted_instance import SelfHostedInstanceEntity
|
|
5
8
|
from thestage.services.core_files.config_entity import ConfigEntity
|
|
6
9
|
from thestage.i18n.translation import __
|
|
7
10
|
from thestage.services.clients.thestage_api.dtos.enums.selfhosted_status import SelfhostedBusinessStatus
|
|
@@ -13,6 +16,8 @@ from thestage.services.clients.thestage_api.dtos.instance_rented_response import
|
|
|
13
16
|
from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
|
|
14
17
|
from thestage.services.clients.thestage_api.dtos.selfhosted_instance_response import SelfHostedInstanceDto
|
|
15
18
|
from thestage.services.config_provider.config_provider import ConfigProvider
|
|
19
|
+
from thestage.services.instance.mapper.instance_mapper import InstanceMapper
|
|
20
|
+
from thestage.services.instance.mapper.selfhosted_mapper import SelfHostedMapper
|
|
16
21
|
from thestage.services.remote_server_service import RemoteServerService
|
|
17
22
|
|
|
18
23
|
|
|
@@ -32,22 +37,22 @@ class InstanceService(AbstractService):
|
|
|
32
37
|
self.__thestage_api_client = thestage_api_client
|
|
33
38
|
self.__remote_server_service = remote_server_service
|
|
34
39
|
|
|
35
|
-
def
|
|
40
|
+
def get_rented_instance(
|
|
36
41
|
self,
|
|
37
42
|
config: ConfigEntity,
|
|
38
43
|
instance_slug: str,
|
|
39
44
|
) -> Optional[InstanceRentedDto]:
|
|
40
|
-
return self.__thestage_api_client.
|
|
45
|
+
return self.__thestage_api_client.get_rented_instance(
|
|
41
46
|
token=config.main.thestage_auth_token,
|
|
42
47
|
instance_slug=instance_slug,
|
|
43
48
|
)
|
|
44
49
|
|
|
45
|
-
def
|
|
50
|
+
def get_self_hosted_instance(
|
|
46
51
|
self,
|
|
47
52
|
config: ConfigEntity,
|
|
48
53
|
instance_slug: str,
|
|
49
54
|
) -> Optional[SelfHostedInstanceDto]:
|
|
50
|
-
return self.__thestage_api_client.
|
|
55
|
+
return self.__thestage_api_client.get_selfhosted_instance(
|
|
51
56
|
token=config.main.thestage_auth_token,
|
|
52
57
|
instance_slug=instance_slug,
|
|
53
58
|
)
|
|
@@ -121,7 +126,7 @@ class InstanceService(AbstractService):
|
|
|
121
126
|
config: ConfigEntity,
|
|
122
127
|
input_ssh_key_path: Optional[str]
|
|
123
128
|
):
|
|
124
|
-
instance = self.
|
|
129
|
+
instance = self.get_rented_instance(config=config, instance_slug=instance_rented_slug)
|
|
125
130
|
|
|
126
131
|
if instance:
|
|
127
132
|
self.check_instance_status_to_connect(
|
|
@@ -162,7 +167,7 @@ class InstanceService(AbstractService):
|
|
|
162
167
|
username = 'root'
|
|
163
168
|
typer.echo(__("No remote server username provided, using 'root' as username"))
|
|
164
169
|
|
|
165
|
-
instance = self.
|
|
170
|
+
instance = self.get_self_hosted_instance(config=config, instance_slug=selfhosted_instance_slug)
|
|
166
171
|
|
|
167
172
|
if instance:
|
|
168
173
|
self.check_selfhosted_status_to_connect(
|
|
@@ -222,3 +227,92 @@ class InstanceService(AbstractService):
|
|
|
222
227
|
limit=row,
|
|
223
228
|
)
|
|
224
229
|
return data
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@error_handler()
|
|
233
|
+
def print_self_hosted_instance_list(self, config: ConfigEntity, statuses, row, page):
|
|
234
|
+
selfhosted_instance_status_map = self.__thestage_api_client.get_selfhosted_business_status_map(config.main.thestage_auth_token)
|
|
235
|
+
|
|
236
|
+
if not statuses:
|
|
237
|
+
statuses = ({key: selfhosted_instance_status_map[key] for key in [
|
|
238
|
+
SelfhostedBusinessStatus.AWAITING_CONFIGURATION,
|
|
239
|
+
SelfhostedBusinessStatus.RUNNING,
|
|
240
|
+
SelfhostedBusinessStatus.TERMINATED,
|
|
241
|
+
]}).values()
|
|
242
|
+
|
|
243
|
+
if "all" in statuses:
|
|
244
|
+
statuses = selfhosted_instance_status_map.values()
|
|
245
|
+
|
|
246
|
+
for input_status_item in statuses:
|
|
247
|
+
if input_status_item not in selfhosted_instance_status_map.values():
|
|
248
|
+
typer.echo(__("'%invalid_status%' is not one of %valid_statuses%", {
|
|
249
|
+
'invalid_status': input_status_item,
|
|
250
|
+
'valid_statuses': str(list(selfhosted_instance_status_map.values()))
|
|
251
|
+
}))
|
|
252
|
+
raise typer.Exit(1)
|
|
253
|
+
|
|
254
|
+
typer.echo(__(
|
|
255
|
+
"Listing self-hosted instances with the following statuses: %statuses%, to view all self-hosted instances, use --status all",
|
|
256
|
+
placeholders={
|
|
257
|
+
'statuses': ', '.join([status_item for status_item in statuses])
|
|
258
|
+
}))
|
|
259
|
+
|
|
260
|
+
backend_statuses: List[str] = [key for key, value in selfhosted_instance_status_map.items() if value in statuses]
|
|
261
|
+
|
|
262
|
+
self.print(
|
|
263
|
+
func_get_data=self.get_self_hosted_list,
|
|
264
|
+
func_special_params={
|
|
265
|
+
'statuses': backend_statuses,
|
|
266
|
+
},
|
|
267
|
+
mapper=SelfHostedMapper(),
|
|
268
|
+
config=config,
|
|
269
|
+
headers=list(map(lambda x: x.alias, SelfHostedInstanceEntity.model_fields.values())),
|
|
270
|
+
row=row,
|
|
271
|
+
page=page,
|
|
272
|
+
show_index="never",
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@error_handler()
|
|
277
|
+
def print_rented_instance_list(self, config: ConfigEntity, statuses, row, page):
|
|
278
|
+
instance_rented_status_map = self.__thestage_api_client.get_rented_business_status_map(config.main.thestage_auth_token)
|
|
279
|
+
|
|
280
|
+
if not statuses:
|
|
281
|
+
statuses = ({key: instance_rented_status_map[key] for key in [
|
|
282
|
+
InstanceRentedBusinessStatus.ONLINE,
|
|
283
|
+
InstanceRentedBusinessStatus.CREATING,
|
|
284
|
+
InstanceRentedBusinessStatus.TERMINATING,
|
|
285
|
+
InstanceRentedBusinessStatus.REBOOTING,
|
|
286
|
+
]}).values()
|
|
287
|
+
|
|
288
|
+
if "all" in statuses:
|
|
289
|
+
statuses = instance_rented_status_map.values()
|
|
290
|
+
|
|
291
|
+
for input_status_item in statuses:
|
|
292
|
+
if input_status_item not in instance_rented_status_map.values():
|
|
293
|
+
typer.echo(__("'%invalid_status%' is not one of %valid_statuses%", {
|
|
294
|
+
'invalid_status': input_status_item,
|
|
295
|
+
'valid_statuses': str(list(instance_rented_status_map.values()))
|
|
296
|
+
}))
|
|
297
|
+
raise typer.Exit(1)
|
|
298
|
+
|
|
299
|
+
typer.echo(__(
|
|
300
|
+
"Listing rented server instances with the following statuses: %statuses%, to view all rented server instances, use --status all",
|
|
301
|
+
placeholders={
|
|
302
|
+
'statuses': ', '.join([status_item for status_item in statuses])
|
|
303
|
+
}))
|
|
304
|
+
|
|
305
|
+
backend_statuses: List[str] = [key for key, value in instance_rented_status_map.items() if value in statuses]
|
|
306
|
+
|
|
307
|
+
self.print(
|
|
308
|
+
func_get_data=self.get_rented_list,
|
|
309
|
+
func_special_params={
|
|
310
|
+
'statuses': backend_statuses,
|
|
311
|
+
},
|
|
312
|
+
mapper=InstanceMapper(),
|
|
313
|
+
config=config,
|
|
314
|
+
headers=list(map(lambda x: x.alias, RentedInstanceEntity.model_fields.values())),
|
|
315
|
+
row=row,
|
|
316
|
+
page=page,
|
|
317
|
+
show_index="never",
|
|
318
|
+
)
|
|
@@ -151,7 +151,7 @@ class LoggingService(AbstractService):
|
|
|
151
151
|
print_logs_task.cancel()
|
|
152
152
|
if not input_task.result(): # result is only expected if ctrl+D triggered EOFError
|
|
153
153
|
typer.echo(f"\rTask {task_id} will be canceled")
|
|
154
|
-
self.__thestage_api_client.
|
|
154
|
+
self.__thestage_api_client.cancel_task(
|
|
155
155
|
token=config.main.thestage_auth_token,
|
|
156
156
|
task_id=task.id,
|
|
157
157
|
)
|