thestage 0.5.46__py3-none-any.whl → 0.5.47__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 (49) hide show
  1. thestage/.env +5 -0
  2. thestage/__init__.py +2 -1
  3. thestage/cli_command.py +56 -0
  4. thestage/cli_command_helper.py +51 -0
  5. thestage/config/config_storage.py +3 -0
  6. thestage/controllers/base_controller.py +19 -15
  7. thestage/controllers/config_controller.py +30 -38
  8. thestage/controllers/container_controller.py +43 -79
  9. thestage/controllers/instance_controller.py +23 -40
  10. thestage/controllers/project_controller.py +95 -184
  11. thestage/controllers/utils_controller.py +12 -8
  12. thestage/debug_tests.py +12 -0
  13. thestage/entities/container.py +0 -5
  14. thestage/entities/rented_instance.py +0 -5
  15. thestage/entities/self_hosted_instance.py +0 -2
  16. thestage/helpers/error_handler.py +14 -14
  17. thestage/helpers/exception_hook.py +14 -0
  18. thestage/helpers/logger/app_logger.py +3 -4
  19. thestage/main.py +25 -13
  20. thestage/services/abstract_service.py +0 -40
  21. thestage/services/app_config_service.py +7 -6
  22. thestage/services/clients/.DS_Store +0 -0
  23. thestage/services/clients/thestage_api/api_client.py +54 -68
  24. thestage/services/clients/thestage_api/core/api_client_core.py +92 -9
  25. thestage/services/clients/thestage_api/dtos/container_response.py +1 -1
  26. thestage/services/clients/thestage_api/dtos/inference_simulator_model_response.py +1 -1
  27. thestage/services/clients/thestage_api/dtos/inference_simulator_response.py +1 -1
  28. thestage/services/clients/thestage_api/dtos/instance_rented_response.py +1 -1
  29. thestage/services/clients/thestage_api/dtos/selfhosted_instance_response.py +1 -1
  30. thestage/services/clients/thestage_api/dtos/task_controller/task_status_localized_map_response.py +1 -1
  31. thestage/services/clients/thestage_api/dtos/validate_token_response.py +11 -0
  32. thestage/services/config_provider/config_provider.py +75 -47
  33. thestage/services/connect/connect_service.py +11 -22
  34. thestage/services/container/container_service.py +6 -23
  35. thestage/services/core_files/config_entity.py +7 -1
  36. thestage/services/filesystem_service.py +2 -2
  37. thestage/services/instance/instance_service.py +12 -26
  38. thestage/services/logging/logging_service.py +17 -45
  39. thestage/services/project/project_service.py +33 -72
  40. thestage/services/remote_server_service.py +3 -4
  41. thestage/services/service_factory.py +21 -27
  42. thestage/services/validation_service.py +14 -9
  43. {thestage-0.5.46.dist-info → thestage-0.5.47.dist-info}/METADATA +3 -4
  44. {thestage-0.5.46.dist-info → thestage-0.5.47.dist-info}/RECORD +47 -41
  45. {thestage-0.5.46.dist-info → thestage-0.5.47.dist-info}/WHEEL +1 -1
  46. thestage/exceptions/http_error_exception.py +0 -12
  47. thestage/services/clients/thestage_api/core/api_client_abstract.py +0 -91
  48. {thestage-0.5.46.dist-info → thestage-0.5.47.dist-info}/LICENSE.txt +0 -0
  49. {thestage-0.5.46.dist-info → thestage-0.5.47.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,11 @@
1
+ from typing import Optional, List, Dict
2
+
3
+ from pydantic import Field, BaseModel, ConfigDict
4
+
5
+ from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse, TheStageBasePaginatedResponse
6
+
7
+
8
+ class ValidateTokenResponse(TheStageBaseResponse):
9
+ model_config = ConfigDict(use_enum_values=True)
10
+
11
+ allowedCliCommands: Optional[Dict[str, bool]] = Field(default={}, alias='allowedCliCommands')
@@ -2,50 +2,40 @@ import typer
2
2
 
3
3
  import json
4
4
  import os
5
- from json import JSONDecodeError
6
5
  from pathlib import Path
7
6
  from typing import Optional, Dict, Any
8
7
 
9
- from thestage.exceptions.file_system_exception import FileSystemException
8
+ from thestage.cli_command import CliCommand, CliCommandAvailability, ALWAYS_AVAILABLE_COMMANDS
9
+ from thestage.services.clients.thestage_api.dtos.validate_token_response import ValidateTokenResponse
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
13
  from thestage.services.filesystem_service import FileSystemService
14
14
  from thestage.services.project.dto.project_config import ProjectConfig
15
- from thestage.config import THESTAGE_CONFIG_DIR, THESTAGE_CONFIG_FILE, THESTAGE_AUTH_TOKEN, THESTAGE_API_URL
16
-
17
- class ConfigProvider():
18
- # path to current physical subject we work with (project / etc) - might be passed explicitly
19
- _local_path: Optional[Path] = None
20
- _local_config_path: Optional[Path] = None
21
- _global_config_path: Optional[Path] = None
22
- _global_config_file: Optional[Path] = None
15
+ from thestage.config import THESTAGE_CONFIG_DIR, THESTAGE_CONFIG_FILE, THESTAGE_AUTH_TOKEN, THESTAGE_API_URL, \
16
+ config_storage
17
+
18
+
19
+ class ConfigProvider:
23
20
  _file_system_service = FileSystemService
24
21
 
25
22
 
26
23
  def __init__(
27
24
  self,
28
- local_path: Optional[str] = None,
25
+ file_system_service: FileSystemService,
29
26
  ):
30
- self._file_system_service = FileSystemService()
31
- if local_path:
32
- self._local_path = self._file_system_service.get_path(directory=local_path, auto_create=False)
33
-
34
- self._local_config_path = self._local_path.joinpath(THESTAGE_CONFIG_DIR)
35
-
36
- home_dir = self._file_system_service.get_home_path()
37
- self._global_config_path = home_dir.joinpath(THESTAGE_CONFIG_DIR)
27
+ self._file_system_service = file_system_service
38
28
 
39
- if self._global_config_path:
40
- if not self._global_config_path.exists():
41
- self._file_system_service.create_if_not_exists_dir(self._global_config_path)
29
+ global_config_path = self.get_global_config_path()
30
+ if not global_config_path.exists():
31
+ self._file_system_service.create_if_not_exists_dir(global_config_path)
42
32
 
43
- self._global_config_file = self._global_config_path.joinpath(THESTAGE_CONFIG_FILE)
44
- if not self._global_config_file.exists():
45
- self._file_system_service.create_if_not_exists_file(self._global_config_file)
33
+ global_config_file = self.get_global_config_file()
34
+ self._file_system_service.create_if_not_exists_file(global_config_file)
46
35
 
47
36
 
48
- def get_full_config(self) -> ConfigEntity:
37
+ # must be called right after the app is started
38
+ def build_config(self) -> ConfigEntity:
49
39
  config_values = {}
50
40
 
51
41
  config_from_env = {}
@@ -58,35 +48,36 @@ class ConfigProvider():
58
48
  if config_from_env:
59
49
  self.__update_config_values_dict(values_to_update=config_values, new_values=config_from_env)
60
50
 
61
- config_from_file = self._file_system_service.read_config_file(self._global_config_file)
51
+ config_from_file = self._file_system_service.read_config_file(self.get_global_config_file())
62
52
  if config_from_file:
63
53
  self.__update_config_values_dict(values_to_update=config_values, new_values=config_from_file)
64
54
 
65
55
  config = ConfigEntity.model_validate(config_values)
56
+ config.runtime.config_global_path = str(self.get_global_config_path())
66
57
 
67
- if self._local_path:
68
- config.runtime.working_directory = str(self._local_path)
69
- if self._global_config_path and not config.runtime.config_global_path:
70
- config.runtime.config_global_path = str(self._global_config_path)
58
+ config_storage.APP_CONFIG = config
71
59
 
72
60
  return config
73
61
 
74
62
 
63
+ def get_config(self) -> ConfigEntity:
64
+ if not config_storage.APP_CONFIG:
65
+ raise Exception('Config storage was not initialized')
66
+ return config_storage.APP_CONFIG
67
+
68
+
75
69
  def save_project_config(self, project_config: ProjectConfig):
76
- project_data_dirpath = self.__get_project_config_path()
77
- if not project_data_dirpath.exists():
78
- self._file_system_service.create_if_not_exists_dir(project_data_dirpath)
70
+ project_data_dirpath = self.__get_project_config_path(with_file=False)
71
+ self._file_system_service.create_if_not_exists_dir(project_data_dirpath)
79
72
 
80
73
  project_data_filepath = self.__get_project_config_path(with_file=True)
81
- if not project_data_filepath.exists():
82
- self._file_system_service.create_if_not_exists_file(project_data_filepath)
74
+ self._file_system_service.create_if_not_exists_file(project_data_filepath)
83
75
 
84
- project_config_path = self.__get_project_config_path(with_file=True)
85
- self.__save_config_file(data=project_config.model_dump(), file_path=project_config_path)
76
+ self.__save_config_file(data=project_config.model_dump(), file_path=project_data_filepath)
86
77
 
87
78
 
88
79
  def save_project_deploy_ssh_key(self, deploy_ssh_key: str, project_slug: str, project_id: int) -> str:
89
- deploy_key_dirpath = self._global_config_path.joinpath('project_deploy_keys')
80
+ deploy_key_dirpath = self.get_global_config_path().joinpath('project_deploy_keys')
90
81
  self._file_system_service.create_if_not_exists_dir(deploy_key_dirpath)
91
82
 
92
83
  deploy_key_filepath = deploy_key_dirpath.joinpath(f'project_deploy_key_{project_id}_{project_slug}')
@@ -115,13 +106,13 @@ class ConfigProvider():
115
106
 
116
107
  def __get_project_config_path(self, with_file: bool = False) -> Path:
117
108
  if with_file:
118
- return self._local_config_path.joinpath('project.json')
109
+ return Path(self.get_config().runtime.working_directory).joinpath(THESTAGE_CONFIG_DIR).joinpath('project.json')
119
110
  else:
120
- return self._local_config_path
111
+ return Path(self.get_config().runtime.working_directory).joinpath(THESTAGE_CONFIG_DIR)
121
112
 
122
113
 
123
114
  def __get_remote_server_config_path(self) -> Path:
124
- return self._global_config_path.joinpath('remote_server_config.json')
115
+ return self.get_global_config_path().joinpath('remote_server_config.json')
125
116
 
126
117
 
127
118
  def update_remote_server_config_entry(self, ip_address: str, ssh_key_path: Optional[Path]):
@@ -191,15 +182,18 @@ class ConfigProvider():
191
182
  json.dump(data, configfile, indent=1)
192
183
 
193
184
 
194
- def save_config(self, config: ConfigEntity):
195
- data: Dict[str, Any] = self._file_system_service.read_config_file(self._global_config_file)
185
+ def save_config(self):
186
+ config = self.get_config()
187
+ global_config_file = self.get_global_config_file()
188
+ data: Dict[str, Any] = self._file_system_service.read_config_file(global_config_file)
196
189
  data.update(config.model_dump(exclude_none=True, by_alias=True, exclude={'runtime', 'RUNTIME', 'daemon', 'DAEMON'}))
197
- self.__save_config_file(data=data, file_path=self._global_config_file)
190
+ self.__save_config_file(data=data, file_path=global_config_file)
198
191
 
199
192
 
200
193
  def clear_config(self, ):
201
- if self._global_config_path and self._global_config_path.exists():
202
- self._file_system_service.remove_folder(str(self._global_config_path))
194
+ global_config_path = self.get_global_config_path()
195
+ if global_config_path.exists():
196
+ self._file_system_service.remove_folder(str(global_config_path))
203
197
 
204
198
  os.unsetenv('THESTAGE_CONFIG_DIR')
205
199
  os.unsetenv('THESTAGE_CONFIG_FILE')
@@ -207,3 +201,37 @@ class ConfigProvider():
207
201
  os.unsetenv('THESTAGE_API_URL')
208
202
  os.unsetenv('THESTAGE_LOG_FILE')
209
203
  os.unsetenv('THESTAGE_AUTH_TOKEN')
204
+
205
+
206
+ def update_config(self, updated_config: ConfigEntity):
207
+ config_storage.APP_CONFIG = updated_config
208
+
209
+ def get_global_config_path(self) -> Path:
210
+ home_dir = self._file_system_service.get_home_path()
211
+ return home_dir.joinpath(THESTAGE_CONFIG_DIR)
212
+
213
+ def get_global_config_file(self) -> Path:
214
+ return self.get_global_config_path().joinpath(THESTAGE_CONFIG_FILE)
215
+
216
+
217
+ def update_allowed_commands_and_is_token_valid(self, validate_token_response: ValidateTokenResponse):
218
+ config = self.get_config()
219
+ for cli_command_item in CliCommand:
220
+ is_command_available = validate_token_response.allowedCliCommands.get(cli_command_item)
221
+ command_availability = CliCommandAvailability.DEPRECATED
222
+
223
+ if cli_command_item in ALWAYS_AVAILABLE_COMMANDS:
224
+ command_availability = CliCommandAvailability.ALLOWED
225
+ else:
226
+ if is_command_available is True:
227
+ command_availability = CliCommandAvailability.ALLOWED
228
+ if is_command_available is False:
229
+ command_availability = CliCommandAvailability.RESTRICTED
230
+ config.runtime.allowed_commands.update({cli_command_item: command_availability})
231
+ config.runtime.is_token_valid = validate_token_response.is_success
232
+ self.update_config(config)
233
+
234
+
235
+ def is_cli_command_allowed(self, cli_command: CliCommand) -> bool:
236
+ config = self.get_config()
237
+ return True if config.runtime.allowed_commands.get(cli_command) == CliCommandAvailability.ALLOWED else False
@@ -1,6 +1,8 @@
1
1
  from typing import Optional
2
2
  import typer
3
3
 
4
+ from thestage.cli_command import CliCommand
5
+ from thestage.cli_command_helper import check_command_permission
4
6
  from thestage.i18n.translation import __
5
7
  from thestage.services.clients.thestage_api.core.http_client_exception import HttpClientException
6
8
  from thestage.services.clients.thestage_api.dtos.enums.container_status import DockerContainerStatus
@@ -11,7 +13,6 @@ from thestage.helpers.error_handler import error_handler
11
13
  from thestage.services.clients.thestage_api.api_client import TheStageApiClient
12
14
  from thestage.services.clients.thestage_api.dtos.enums.task_status import TaskStatus
13
15
  from thestage.services.clients.thestage_api.dtos.instance_rented_response import InstanceRentedDto
14
- from thestage.services.config_provider.config_provider import ConfigProvider
15
16
  from thestage.services.container.container_service import ContainerService
16
17
  from thestage.services.instance.instance_service import InstanceService
17
18
  from thestage.services.logging.logging_service import LoggingService
@@ -26,14 +27,12 @@ class ConnectService(AbstractService):
26
27
 
27
28
  def __init__(
28
29
  self,
29
- config_provider: ConfigProvider,
30
30
  thestage_api_client: TheStageApiClient,
31
31
  instance_service: InstanceService,
32
32
  container_service: ContainerService,
33
33
  logging_service: LoggingService,
34
34
  ):
35
35
  super(ConnectService, self).__init__(
36
- config_provider=config_provider,
37
36
  )
38
37
  self.__thestage_api_client = thestage_api_client
39
38
  self.__instance_service = instance_service
@@ -48,24 +47,22 @@ class ConnectService(AbstractService):
48
47
  username: Optional[str],
49
48
  private_key_path: Optional[str],
50
49
  ):
51
- config = self._config_provider.get_full_config()
52
-
53
50
  try:
54
- instance_selfhosted = self.__thestage_api_client.get_selfhosted_instance(token=config.main.thestage_auth_token, instance_slug=uid)
51
+ instance_selfhosted = self.__thestage_api_client.get_selfhosted_instance(instance_slug=uid)
55
52
  except HttpClientException as e:
56
53
  if e.get_status_code() == 403:
57
54
  typer.echo("Missing permission to view self-hosted instances")
58
55
  instance_selfhosted = None
59
56
 
60
57
  try:
61
- instance_rented = self.__thestage_api_client.get_rented_instance(token=config.main.thestage_auth_token, instance_slug=uid)
58
+ instance_rented = self.__thestage_api_client.get_rented_instance(instance_slug=uid)
62
59
  except HttpClientException as e:
63
60
  if e.get_status_code() == 403:
64
61
  typer.echo("Missing permission to view rented instances")
65
62
  instance_rented = None
66
63
 
67
64
  try:
68
- container = self.__thestage_api_client.get_container(token=config.main.thestage_auth_token, container_slug=uid, )
65
+ container = self.__thestage_api_client.get_container(container_slug=uid,)
69
66
  except HttpClientException as e:
70
67
  if e.get_status_code() == 403:
71
68
  typer.echo("Missing permission to view containers")
@@ -74,7 +71,7 @@ class ConnectService(AbstractService):
74
71
  task: Optional[TaskDto] = None
75
72
  if uid.isdigit():
76
73
  try:
77
- task_view_response = self.__thestage_api_client.get_task(token=config.main.thestage_auth_token, task_id=int(uid))
74
+ task_view_response = self.__thestage_api_client.get_task(task_id=int(uid))
78
75
  except HttpClientException as e:
79
76
  if e.get_status_code() == 403:
80
77
  typer.echo("Missing permission to view tasks")
@@ -114,27 +111,27 @@ class ConnectService(AbstractService):
114
111
  raise typer.Exit(code=1)
115
112
 
116
113
  if rented_presence:
114
+ check_command_permission(CliCommand.INSTANCE_RENTED_CONNECT)
117
115
  typer.echo("Connecting to rented instance...")
118
116
  self.__instance_service.connect_to_rented_instance(
119
117
  instance_rented_slug=uid,
120
- config=config,
121
118
  input_ssh_key_path=private_key_path
122
119
  )
123
120
 
124
121
  if container_presence:
122
+ check_command_permission(CliCommand.CONTAINER_CONNECT)
125
123
  typer.echo("Connecting to docker container...")
126
124
  self.__container_service.connect_to_container(
127
- config=config,
128
125
  container_uid=uid,
129
126
  username=username,
130
127
  input_ssh_key_path=private_key_path
131
128
  )
132
129
 
133
130
  if selfhosted_presence:
131
+ check_command_permission(CliCommand.INSTANCE_SELF_HOSTED_CONNECT)
134
132
  typer.echo("Connecting to self-hosted instance...")
135
133
 
136
134
  self.__instance_service.connect_to_selfhosted_instance(
137
- config=config,
138
135
  selfhosted_instance_slug=uid,
139
136
  username=username,
140
137
  input_ssh_key_path=private_key_path
@@ -142,20 +139,15 @@ class ConnectService(AbstractService):
142
139
 
143
140
  if task_presence:
144
141
  typer.echo(__("Connecting to task..."))
145
- self.__logging_service.stream_task_logs_with_controls(
146
- config=config,
147
- task_id=int(uid),
148
- )
142
+ self.__logging_service.stream_task_logs_with_controls(task_id=int(uid))
149
143
 
150
144
 
151
145
  @error_handler()
152
146
  def upload_ssh_key(self, public_key_contents: str, instance_slug: Optional[str]):
153
- config = self._config_provider.get_full_config()
154
-
155
147
  instance_rented: Optional[InstanceRentedDto] = None
156
148
  if instance_slug:
157
149
  try:
158
- instance_rented = self.__thestage_api_client.get_rented_instance(token=config.main.thestage_auth_token, instance_slug=instance_slug)
150
+ instance_rented = self.__thestage_api_client.get_rented_instance(instance_slug=instance_slug)
159
151
  except HttpClientException as e:
160
152
  instance_rented = None
161
153
 
@@ -167,7 +159,6 @@ class ConnectService(AbstractService):
167
159
  note_to_send: Optional[str] = None
168
160
 
169
161
  is_user_already_has_key_response = self.__thestage_api_client.is_user_has_ssh_public_key(
170
- token=config.main.thestage_auth_token,
171
162
  public_key=public_key_contents
172
163
  )
173
164
 
@@ -187,7 +178,6 @@ class ConnectService(AbstractService):
187
178
 
188
179
  if is_adding_key_to_user:
189
180
  add_ssh_key_to_user_response = self.__thestage_api_client.add_public_ssh_key_to_user(
190
- token=config.main.thestage_auth_token,
191
181
  public_key=public_key_contents,
192
182
  note=note_to_send
193
183
  )
@@ -196,7 +186,6 @@ class ConnectService(AbstractService):
196
186
 
197
187
  if instance_rented:
198
188
  self.__thestage_api_client.add_public_ssh_key_to_instance_rented(
199
- token=config.main.thestage_auth_token,
200
189
  instance_rented_id=instance_rented.id,
201
190
  ssh_key_pair_id=ssh_key_pair_id
202
191
  )
@@ -2,7 +2,6 @@ from pathlib import Path
2
2
  from typing import List, Tuple, Optional, Dict
3
3
 
4
4
  import typer
5
- from thestage.services.core_files.config_entity import ConfigEntity
6
5
  from thestage.entities.container import DockerContainerEntity
7
6
  from thestage.services.clients.thestage_api.dtos.container_param_request import DockerContainerActionRequestDto
8
7
  from thestage.services.clients.thestage_api.dtos.enums.container_pending_action import DockerContainerAction
@@ -23,6 +22,7 @@ from thestage.services.config_provider.config_provider import ConfigProvider
23
22
  class ContainerService(AbstractService):
24
23
 
25
24
  __thestage_api_client: TheStageApiClient = None
25
+ __config_provider: ConfigProvider = None
26
26
 
27
27
  def __init__(
28
28
  self,
@@ -31,9 +31,7 @@ class ContainerService(AbstractService):
31
31
  remote_server_service: RemoteServerService,
32
32
  file_system_service: FileSystemService,
33
33
  ):
34
- super(ContainerService, self).__init__(
35
- config_provider=config_provider
36
- )
34
+ self.__config_provider = config_provider
37
35
  self.__thestage_api_client = thestage_api_client
38
36
  self.__remote_server_service = remote_server_service
39
37
  self.__file_system_service = file_system_service
@@ -42,13 +40,12 @@ class ContainerService(AbstractService):
42
40
  @error_handler()
43
41
  def print_container_list(
44
42
  self,
45
- config: ConfigEntity,
46
43
  row: int,
47
44
  page: int,
48
45
  project_uid: Optional[str],
49
46
  statuses: List[str],
50
47
  ):
51
- container_status_map = self.__thestage_api_client.get_container_business_status_map(config.main.thestage_auth_token)
48
+ container_status_map = self.__thestage_api_client.get_container_business_status_map()
52
49
 
53
50
  if not statuses:
54
51
  statuses = ({key: container_status_map[key] for key in [
@@ -77,7 +74,7 @@ class ContainerService(AbstractService):
77
74
 
78
75
  project_id: Optional[int] = None
79
76
  if project_uid:
80
- project = self.__thestage_api_client.get_project_by_slug(slug=project_uid, token=config.main.thestage_auth_token)
77
+ project = self.__thestage_api_client.get_project_by_slug(slug=project_uid)
81
78
  project_id = project.id
82
79
 
83
80
  self.print(
@@ -87,7 +84,6 @@ class ContainerService(AbstractService):
87
84
  'project_id': project_id,
88
85
  },
89
86
  mapper=ContainerMapper(),
90
- config=config,
91
87
  headers=list(map(lambda x: x.alias, DockerContainerEntity.model_fields.values())),
92
88
  row=row,
93
89
  page=page,
@@ -99,7 +95,6 @@ class ContainerService(AbstractService):
99
95
  @error_handler()
100
96
  def get_list(
101
97
  self,
102
- config: ConfigEntity,
103
98
  statuses: List[str],
104
99
  row: int = 5,
105
100
  page: int = 1,
@@ -107,7 +102,6 @@ class ContainerService(AbstractService):
107
102
  ) -> PaginatedEntityList[DockerContainerDto]:
108
103
 
109
104
  list = self.__thestage_api_client.get_container_list(
110
- token=config.main.thestage_auth_token,
111
105
  statuses=statuses,
112
106
  page=page,
113
107
  limit=row,
@@ -119,12 +113,10 @@ class ContainerService(AbstractService):
119
113
  @error_handler()
120
114
  def get_container(
121
115
  self,
122
- config: ConfigEntity,
123
116
  container_id: Optional[int] = None,
124
117
  container_slug: Optional[str] = None,
125
118
  ) -> Optional[DockerContainerDto]:
126
119
  return self.__thestage_api_client.get_container(
127
- token=config.main.thestage_auth_token,
128
120
  container_id=container_id,
129
121
  container_slug=container_slug,
130
122
  )
@@ -154,13 +146,13 @@ class ContainerService(AbstractService):
154
146
 
155
147
  private_key_path = private_key_path_override
156
148
  if not private_key_path:
157
- private_key_path = self._config_provider.get_valid_private_key_path_by_ip_address(ip_address)
149
+ private_key_path = self.__config_provider.get_valid_private_key_path_by_ip_address(ip_address)
158
150
  if private_key_path:
159
151
  typer.echo(f'Using configured private key for {ip_address}: {private_key_path}')
160
152
  else:
161
153
  typer.echo(f'Using SSH agent to connect to {ip_address}')
162
154
  else:
163
- self._config_provider.update_remote_server_config_entry(ip_address, Path(private_key_path))
155
+ self.__config_provider.update_remote_server_config_entry(ip_address, Path(private_key_path))
164
156
  typer.echo(f'Updated private key path for {ip_address}: {private_key_path}')
165
157
 
166
158
  return username, ip_address, private_key_path
@@ -168,13 +160,11 @@ class ContainerService(AbstractService):
168
160
  @error_handler()
169
161
  def connect_to_container(
170
162
  self,
171
- config: ConfigEntity,
172
163
  container_uid: str,
173
164
  username: Optional[str],
174
165
  input_ssh_key_path: Optional[str],
175
166
  ):
176
167
  container: Optional[DockerContainerDto] = self.get_container(
177
- config=config,
178
168
  container_slug=container_uid,
179
169
  )
180
170
 
@@ -321,7 +311,6 @@ class ContainerService(AbstractService):
321
311
  container: DockerContainerDto,
322
312
  src_path: str,
323
313
  copy_only_folder_contents: bool,
324
- config: ConfigEntity,
325
314
  destination_path: Optional[str] = None,
326
315
  username_param: Optional[str] = None,
327
316
  ):
@@ -344,8 +333,6 @@ class ContainerService(AbstractService):
344
333
  typer.echo(__("Cannot find matching container volume mapping for specified file path"))
345
334
  raise typer.Exit(1)
346
335
 
347
-
348
-
349
336
  self.__remote_server_service.download_data_from_container(
350
337
  ip_address=ip_address,
351
338
  username=username,
@@ -353,19 +340,16 @@ class ContainerService(AbstractService):
353
340
  instance_path=instance_path,
354
341
  copy_only_folder_contents=copy_only_folder_contents,
355
342
  private_key_path=private_key_path,
356
- config=config,
357
343
  )
358
344
 
359
345
 
360
346
  @error_handler()
361
347
  def request_docker_container_action(
362
348
  self,
363
- config: ConfigEntity,
364
349
  container_uid: str,
365
350
  action: DockerContainerAction,
366
351
  ):
367
352
  container: Optional[DockerContainerDto] = self.get_container(
368
- config=config,
369
353
  container_slug=container_uid,
370
354
  )
371
355
  if not container:
@@ -383,7 +367,6 @@ class ContainerService(AbstractService):
383
367
  action=action,
384
368
  )
385
369
  result = self.__thestage_api_client.container_action(
386
- token=config.main.thestage_auth_token,
387
370
  request_param=request_params,
388
371
  )
389
372
 
@@ -1,16 +1,22 @@
1
- from typing import Optional
1
+ from typing import Optional, Dict
2
2
 
3
3
  from pydantic import BaseModel, Field
4
4
 
5
+ from thestage.cli_command import CliCommand, CliCommandAvailability
6
+
7
+
5
8
  # saved to file
6
9
  class MainConfigEntity(BaseModel):
7
10
  thestage_auth_token: Optional[str] = Field(None, alias='thestage_auth_token')
8
11
  thestage_api_url: Optional[str] = Field(None, alias='thestage_api_url')
9
12
 
13
+
10
14
  # not saved to file
11
15
  class RuntimeConfigEntity(BaseModel):
12
16
  working_directory: Optional[str] = Field(None, alias='working_directory')
13
17
  config_global_path: Optional[str] = Field(None, alias='config_global_path')
18
+ allowed_commands: Dict[CliCommand, CliCommandAvailability] = {}
19
+ is_token_valid: bool = Field(None, alias='is_token_valid')
14
20
 
15
21
 
16
22
  class ConfigEntity(BaseModel):
@@ -126,8 +126,8 @@ class FileSystemService:
126
126
  try:
127
127
  if os.stat(path).st_size != 0:
128
128
  result = json.load(file)
129
- except JSONDecodeError:
130
- pass
129
+ except JSONDecodeError as e:
130
+ raise Exception(f"Config file is malformed: {path}") from e
131
131
  except OSError:
132
132
  raise FileSystemException(f"Could not open config file: {path}")
133
133
  return result