perxis.extensions.extension_setup
Модуль содержит класс ExtensionSetup. Он используется для управления всеми данными расширения
1""" 2Модуль содержит класс ExtensionSetup. Он используется для управления всеми данными расширения 3""" 4 5 6import logging 7 8import grpc 9import json 10import time 11import copy 12import typing 13 14 15from deepdiff import DeepDiff 16from google.protobuf.json_format import MessageToDict 17from perxis.collections import collections_pb2_grpc, collections_pb2 18from perxis.roles import roles_pb2_grpc, roles_pb2 19from perxis.items import items_pb2_grpc, items_pb2 20from perxis.common import common_pb2 21from perxis.clients import clients_pb2_grpc, clients_pb2 22from perxis.environments import environments_pb2_grpc, environments_pb2 23from perxis.extensions.actions import make_action_item, ACTIONS_COLLECTION_ID 24from perxis.extensions.item_models import AbstractItem, SyncPolicyItem 25from perxis.provider import PerxisItemsWrapper 26 27 28logger = logging.getLogger(__name__) 29 30 31class ExtensionSetup: 32 """ 33 Attributes: 34 collections_service (collections_pb2_grpc.CollectionsStub): ссылка на сервис коллекций 35 environments_service (environments_pb2_grpc.EnvironmentsStub): ссылка на сервис окружений 36 roles_service (roles_pb2_grpc.RolesStub): ссылка на сервис ролей 37 clients_service (clients_pb2_grpc.ClientsStub): ссылка на сервис клиентов 38 items_service (items_pb2_grpc.ItemsStub): ссылка на сервис item'ов 39 collections (list[collections_pb2.Collection]): список коллекций расширения 40 clients (list[clients_pb2.Client]): список клиентов расширения 41 roles (list[roles_pb2.Role]): список ролей расширения 42 items (list[AbstractItem]): список item'ов расширения 43 __max_attempts_count (int): макс. кол-во попыток записи 44 __sleep_time (int): время ожидания перед попыткой 45 46 """ 47 48 def __init__( 49 self, 50 collections_service: collections_pb2_grpc.CollectionsStub, 51 environments_service: environments_pb2_grpc.EnvironmentsStub, 52 roles_service: roles_pb2_grpc.RolesStub, 53 clients_service: clients_pb2_grpc.ClientsStub, 54 items_service: items_pb2_grpc.ItemsStub, 55 ): 56 self.collections = [] 57 self.clients = [] 58 self.roles = [] 59 self.actions = [] 60 self.items = [] 61 62 self.roles_service = roles_service 63 self.clients_service = clients_service 64 self.collections_service = collections_service 65 self.environments_service = environments_service 66 self.items_service = items_service 67 68 self.__max_attempts_count = 5 69 self.__sleep_time = 1 70 71 def add_action(self, action: dict): 72 self.actions.append(action) 73 74 def set_actions(self, actions: list[dict]): 75 self.actions = actions 76 77 def add_collection(self, collection: collections_pb2.Collection): 78 self.collections.append(collection) 79 80 def set_collections(self, collections: list[collections_pb2.Collection]): 81 self.collections = collections 82 83 def add_role(self, role: roles_pb2.Role): 84 self.roles.append(role) 85 86 def set_roles(self, roles: list[roles_pb2.Role]): 87 self.roles = roles 88 89 def add_client(self, client: clients_pb2.Client): 90 self.clients.append(client) 91 92 def set_clients(self, clients: list[clients_pb2.Client]): 93 self.clients = clients 94 95 def set_items(self, items: list[AbstractItem]): 96 self.items = items 97 98 # Работа с ролями 99 async def __remove_roles(self, space_id: str) -> list[str]: 100 """ 101 Метод для удаления ролей из пространства. Возвращает массив с ошибками 102 103 Arguments: 104 space_id (str): идентификатор пространства 105 106 Returns: 107 list[str] 108 """ 109 110 errors_list: list[str] = [] 111 112 for role in self.roles: 113 try: 114 await self.roles_service.Delete( 115 roles_pb2.DeleteRequest( 116 space_id=space_id, role_id=role.id 117 ) 118 ) 119 except grpc.RpcError as e: 120 # Если роли не существует считать это ошибкой не надо 121 if "not found" not in e.details(): 122 errors_list.append(f"Не удалось удалить роль {role.id}, {e.details()}") 123 124 return errors_list 125 126 async def __check_roles(self, space_id: str) -> list[str]: 127 """ 128 Метод для проверки ролей из пространства. Возвращает массив с ошибками 129 130 Arguments: 131 space_id (str): идентификатор пространства 132 133 Returns: 134 list[str] 135 """ 136 137 errors_list = [] 138 139 for role in self.roles: 140 try: 141 await self.roles_service.Get( 142 roles_pb2.GetRequest(space_id=space_id, role_id=role.id) 143 ) 144 except grpc.RpcError as e: 145 errors_list.append(f"Не удалось получить роль {role.id}, ошибка {e.details()}") 146 147 return errors_list 148 149 async def __update_roles(self, space_id: str, env_id: str) -> list[str]: 150 """ 151 Метод для установки ролей в пространство. Возвращает массив с ошибками. Права доступа 152 которые были установлены в perxis вручную или другим расширением не затираются. 153 154 Arguments: 155 space_id (str): идентификатор пространства 156 env_id (str): идентификатор окружения 157 158 Returns: 159 list[str] 160 """ 161 162 errors_list = [] 163 164 for local_role in self.roles: 165 try: 166 get_response = await self.roles_service.Get( 167 roles_pb2.GetRequest( 168 space_id=space_id, 169 role_id=local_role.id 170 ) 171 ) 172 173 role = get_response.role 174 except grpc.RpcError as e: 175 if "not found" not in e.details(): 176 errors_list.append(f"Не удалось получить роль {local_role.id}, {e.details()}") 177 continue 178 179 role = None 180 181 cloned_role = copy.deepcopy(local_role) 182 cloned_role.space_id = space_id 183 184 if role: 185 if not role.environments: 186 role.environments[:] = [env_id] 187 elif env_id not in role.environments: 188 role.environments.append(env_id) 189 190 cloned_role.environments[:] = role.environments 191 192 # Произвести мерж правил доступа 193 for exist_rule in role.rules: 194 was_found = False 195 196 for local_rule in cloned_role.rules: 197 # Если запись с правами доступа на коллекцию найдена - правила нужно совместить 198 if local_rule.collection_id == exist_rule.collection_id: 199 was_found = True 200 201 local_rule.actions[:] = list(set(list(local_rule.actions) + list(exist_rule.actions))) 202 203 break 204 205 # Если правило для коллекций не было найдено - его нужно добавить 206 if not was_found: 207 cloned_role.rules.append(common_pb2.Rule( 208 collection_id=exist_rule.collection_id, 209 actions=list(exist_rule.actions) 210 )) 211 212 try: 213 await self.roles_service.Update( 214 roles_pb2.UpdateRequest( 215 role=cloned_role 216 ) 217 ) 218 except grpc.RpcError as e: 219 errors_list.append(f"Не удалось обновить роль {local_role.id}, {e.details()}") 220 else: 221 if env_id not in cloned_role.environments: 222 cloned_role.environments.append(env_id) 223 224 try: 225 cloned_role = copy.deepcopy(local_role) 226 cloned_role.space_id = space_id 227 228 response = await self.roles_service.Create( 229 roles_pb2.CreateRequest( 230 role=cloned_role 231 ), 232 ) 233 except grpc.RpcError as e: 234 # На этапе install считается что ролей __нет__. При install с указанием force роли предварительно 235 # удаляются 236 errors_list.append(f"Не удалось создать роль {local_role.id}, {e.details()}") 237 238 return errors_list 239 240 # Работа с клиентами 241 async def __check_clients(self, space_id: str) -> list[str]: 242 """ 243 Метод для проверки клиентов в пространстве. Возвращает массив с ошибками 244 245 Arguments: 246 space_id (str): идентификатор пространства 247 248 Returns: 249 list[str] 250 """ 251 252 errors_list = [] 253 254 for client in self.clients: 255 try: 256 await self.clients_service.Get( 257 clients_pb2.GetRequest(space_id=space_id, id=client.id) 258 ) 259 except grpc.RpcError as e: 260 errors_list.append(f"Не удалось получить клиент {client.id}, ошибка {e.details()}") 261 262 return errors_list 263 264 async def __remove_clients(self, space_id: str) -> list[str]: 265 """ 266 Метод для удаления клиентов из пространства. Возвращает массив с ошибками 267 268 Arguments: 269 space_id (str): идентификатор пространства 270 271 Returns: 272 list[str] 273 """ 274 275 errors_list: list[str] = [] 276 277 for client in self.clients: 278 try: 279 await self.clients_service.Delete( 280 clients_pb2.DeleteRequest( 281 space_id=space_id, id=client.id 282 ) 283 ) 284 except grpc.RpcError as e: 285 # Отсутствие клиента ошибкой не считается 286 if "not found" not in e.details(): 287 errors_list.append(f"Не удалось удалить клиент {client.id}, {e.details()}") 288 289 return errors_list 290 291 async def __update_clients(self, space_id: str) -> list[str]: 292 """ 293 Метод для создания / обновления клиентов в пространстве. Возвращает массив с ошибками. 294 Свойства oauth и api_key не затираются в случае если клиент уже был создан 295 296 Arguments: 297 space_id (str): идентификатор пространства 298 299 Returns: 300 list[str] 301 """ 302 303 errors_list = [] 304 305 for local_client in self.clients: 306 try: 307 # Перед обновлением клиента предварительно нужно получить текущую запись чтобы скопировать оттуда 308 # токены. Иначе после обновления расширения перестанут работать все приложения которые использовали 309 # токен клиента 310 get_response = await self.clients_service.Get( 311 clients_pb2.GetRequest( 312 space_id=space_id, 313 id=local_client.id 314 ) 315 ) 316 317 client = get_response.client 318 except grpc.RpcError as e: 319 if "not found" not in e.details(): 320 errors_list.append(f"Не удалось получить клиент {local_client.id}, {e.details()}") 321 continue 322 323 client = None 324 325 if client: 326 try: 327 # Нужно чтобы у клиента каждый раз не слетали данные токенов при переустановке 328 # свойства oauth, api_key и tls должны браться из __созданного__ клиента 329 new_client = clients_pb2.Client( 330 id=local_client.id, 331 space_id=space_id, 332 name=local_client.name, 333 description=local_client.description, 334 disabled=client.disabled, 335 role_id=local_client.role_id, 336 oauth=client.oauth or local_client.oauth, 337 api_key=client.api_key or local_client.api_key, 338 tls=client.tls 339 ) 340 341 await self.clients_service.Update( 342 clients_pb2.UpdateRequest( 343 client=new_client 344 ) 345 ) 346 except grpc.RpcError as e: 347 errors_list.append(f"Не удалось обновить клиент {local_client.id}, {e.details()}") 348 else: 349 try: 350 cloned_client = copy.deepcopy(local_client) 351 cloned_client.space_id = space_id 352 353 await self.clients_service.Create( 354 clients_pb2.CreateRequest( 355 client=cloned_client 356 ), 357 ) 358 except grpc.RpcError as e: 359 errors_list.append(f"Не удалось создать клиент {local_client.id}, {e.details()}") 360 361 return errors_list 362 363 # Работа с коллекциями 364 async def __remove_collections(self, space_id: str, env_id: str) -> list[str]: 365 """ 366 Метод для удаления коллекций из определённого окружения пространства. Возвращает массив с ошибками 367 368 Arguments: 369 space_id (str): идентификатор пространства 370 env_id (str): идентификатор окружения 371 372 Returns: 373 list[str] 374 """ 375 376 errors_list: list[str] = [] 377 378 for collection in self.collections: 379 try: 380 await self.collections_service.Delete( 381 collections_pb2.DeleteRequest( 382 space_id=space_id, env_id=env_id, collection_id=collection.id 383 ) 384 ) 385 except grpc.RpcError as e: 386 # Отсутствие коллекции это не ошибка 387 if "not found" not in e.details(): 388 errors_list.append(f"Не удалось удалить коллекцию {collection.id}, {e.details()}") 389 390 return errors_list 391 392 async def __check_collections(self, space_id: str, env_id: str) -> list[str]: 393 """ 394 Метод для проверки коллекций из определённого окружения пространства. Возвращает массив с ошибками 395 396 Arguments: 397 space_id (str): идентификатор пространства 398 env_id (str): идентификатор окружения 399 400 Returns: 401 list[str] 402 """ 403 404 errors_list = [] 405 406 for collection in self.collections: 407 try: 408 await self.collections_service.Get( 409 collections_pb2.GetRequest(space_id=space_id, env_id=env_id, collection_id=collection.id) 410 ) 411 except grpc.RpcError as e: 412 errors_list.append(f"Не удалось получить коллекцию {collection.id}, ошибка {e.details()}") 413 414 return errors_list 415 416 async def __update_collections(self, space_id: str, env_id: str) -> list[str]: 417 """ 418 Метод __обновления__ коллекций. Миграция окружения требуется 419 только в случае если одна или несколько схем коллекций изменялись. Алгоритм работы: 420 1. Получить фактически существующую коллекцию из БД 421 2. Обновить её в perxis 422 3. Сравнить схему коллекции в расширении и в perxis 423 4. Если схема изменена - обновить схему в perxis 424 5. Если обновлялась хотя бы одна схема в perxis - запустить миграцию окружения 425 426 Arguments: 427 space_id (str): идентификатор пространства 428 env_id (str): идентификатор окружения 429 430 Returns: 431 list[str] 432 """ 433 434 need_to_migrate_environment = False 435 errors_list = [] 436 437 for local_collection in self.collections: 438 try: 439 # Необходимо получить текущую версию коллекции для того чтобы сравнить схемы 440 get_response = await self.collections_service.Get( 441 collections_pb2.GetRequest(space_id=space_id, env_id=env_id, collection_id=local_collection.id) 442 ) 443 444 collection = get_response.collection 445 except grpc.RpcError as e: 446 collection = None 447 448 cloned_collection = copy.deepcopy(local_collection) 449 cloned_collection.space_id = space_id 450 cloned_collection.env_id = env_id 451 452 # Коллекция может быть не найдена в случае если она добавлена в новой версии 453 if not collection: 454 try: 455 create_response = await self.collections_service.Create( 456 collections_pb2.CreateRequest(collection=cloned_collection) 457 ) 458 459 collection = create_response.created 460 except grpc.RpcError as e: 461 errors_list.append(f"Не удалось создать коллекцию {local_collection.id}, {e.details()}") 462 463 continue 464 else: 465 # если view-коллекция то не устанавливаем схему 466 if MessageToDict(collection).get("view"): 467 continue 468 try: 469 cloned_collection = copy.deepcopy(local_collection) 470 cloned_collection.space_id = space_id 471 cloned_collection.env_id = env_id 472 473 await self.collections_service.Update( 474 collections_pb2.UpdateRequest(collection=cloned_collection) 475 ) 476 except grpc.RpcError as e: 477 errors_list.append(f"Не удалось обновить коллекцию {local_collection.id}, {e.details()}") 478 479 continue 480 481 diff = DeepDiff( 482 json.loads(collection.schema or "{}"), 483 json.loads(local_collection.schema or "{}"), 484 ignore_numeric_type_changes=True, 485 exclude_paths=["root['loaded']"] 486 ) 487 if diff: 488 need_to_migrate_environment = True 489 490 set_schema_error_message = await self.__set_collection_schema( 491 space_id, env_id, local_collection.id, local_collection.schema 492 ) 493 if set_schema_error_message: 494 errors_list.append(set_schema_error_message) 495 496 if need_to_migrate_environment: 497 migrate_environment_error_message = await self.__migrate_environment(space_id, env_id) 498 if migrate_environment_error_message: 499 errors_list.append(migrate_environment_error_message) 500 501 return errors_list 502 503 async def __migrate_environment(self, space_id: str, env_id: str) -> typing.Optional[str]: 504 """ 505 Метод для миграции окружения. 506 Так как perxis может не сразу выставить коллекции / окружению статус ready операцию необходимо выполнять 507 с попытками 508 Arguments: 509 space_id (str): идентификатор пространства 510 env_id (str): идентификатор окружения 511 512 Returns: 513 str | None 514 """ 515 516 attempt = 0 517 is_ok = False 518 error_message = None 519 520 while attempt <= self.__max_attempts_count and not is_ok: 521 time.sleep(self.__sleep_time) 522 523 try: 524 await self.environments_service.Migrate(environments_pb2.MigrateRequest( 525 space_id=space_id, 526 env_id=env_id, 527 options=environments_pb2.MigrateOptions( 528 wait=True 529 ) 530 )) 531 532 is_ok = True 533 except grpc.RpcError as e: 534 # Если не удалось мигрировать окружение по любой причине кроме подготовки - это ошибка 535 if "is preparing" not in e.details(): 536 error_message = e.details() 537 538 # Для принудительного выхода из цикла 539 attempt = self.__max_attempts_count 540 541 attempt += 1 542 543 return error_message 544 545 async def __set_collection_schema(self, space_id: str, env_id: str, collection_id: str, schema: str) -> typing.Optional[str]: 546 """ 547 Метод для установки схемы коллекции в определённом окружении пространства 548 Так как perxis может не сразу выставить коллекции / окружению статус ready операцию необходимо выполнять 549 с попытками 550 Arguments: 551 space_id (str): идентификатор пространства 552 env_id (str): идентификатор окружения 553 collection_id (str): идентификатор коллекции 554 schema (str): схема коллекции в формате json 555 556 Returns: 557 str | None 558 """ 559 560 attempt = 0 561 is_ok = False 562 error_message = None 563 564 while attempt <= self.__max_attempts_count and not is_ok: 565 time.sleep(self.__sleep_time) 566 567 try: 568 await self.collections_service.SetSchema( 569 collections_pb2.SetSchemaRequest( 570 space_id=space_id, 571 env_id=env_id, 572 collection_id=collection_id, 573 schema=schema 574 ) 575 ) 576 577 is_ok = True 578 except grpc.RpcError as e: 579 # Если не удалось установить схему по любой причине кроме подготовки - это ошибка 580 if "is preparing" not in e.details(): 581 error_message = e.details() 582 583 # Для принудительного выхода из цикла 584 attempt = self.__max_attempts_count 585 586 attempt += 1 587 588 return error_message 589 590 # Работа с действиями 591 async def __check_actions(self, space_id: str, env_id: str) -> list[str]: 592 """ 593 Метод для проверки действий из определённого окружения пространства. Возвращает массив с ошибками 594 595 Arguments: 596 space_id (str): идентификатор пространства 597 env_id (str): идентификатор окружения 598 599 Returns: 600 list[str] 601 """ 602 603 errors_list = [] 604 605 ids = [data["id"] for data in self.actions if data.get("id")] 606 607 items = [] 608 609 try: 610 result = await self.items_service.Find( 611 items_pb2.FindRequest( 612 space_id=space_id, 613 env_id=env_id, 614 collection_id=ACTIONS_COLLECTION_ID, 615 filter=items_pb2.Filter(q=[f"id in {ids}"]), 616 options=items_pb2.FindOptions( 617 options=common_pb2.FindOptions( 618 page_num=0, page_size=len(ids) 619 ) 620 ), 621 ) 622 ) 623 624 items = result.items 625 except grpc.RpcError as e: 626 errors_list.append(f"Не удалось получить данные о действиях, ошибка {e.details()}") 627 finally: 628 found_ids = [item.id for item in items] 629 630 for action_id in found_ids: 631 if action_id not in ids: 632 errors_list.append(f"Действие {action_id} не найдено") 633 634 return errors_list 635 636 async def __update_actions(self, space_id: str, env_id: str) -> list[str]: 637 """ 638 Метод для создания / обновления действий из определённого окружения пространства. Возвращает массив с ошибками 639 640 Arguments: 641 space_id (str): идентификатор пространства 642 env_id (str): идентификатор окружения 643 644 Returns: 645 list[str] 646 """ 647 648 errors_list = [] 649 not_found = False 650 651 for action in self.actions: 652 action_item = make_action_item(space_id, env_id, action) 653 654 try: 655 await self.items_service.Update( 656 items_pb2.UpdateRequest( 657 item=items_pb2.Item( 658 id=action_item.id, 659 space_id=space_id, 660 env_id=env_id, 661 collection_id=ACTIONS_COLLECTION_ID, 662 data=action_item.data, 663 ) 664 ) 665 ) 666 except grpc.RpcError as e: 667 if "not found" not in e.details(): 668 errors_list.append(f"Не удалось обновить действие {action_item.id}, {e.details()}") 669 continue 670 671 not_found = True 672 except Exception as e: 673 errors_list.append(f"Не удалось обновить действие {action_item.id}, {e}") 674 675 continue 676 677 if not_found: 678 try: 679 await self.items_service.Create( 680 items_pb2.CreateRequest( 681 item=action_item 682 ) 683 ) 684 except grpc.RpcError as e: 685 errors_list.append(f"Не удалось создать действие {action_item.id}, {e.details()}") 686 except Exception as e: 687 errors_list.append(f"Не удалось создать действие {action_item.id}, {e}") 688 689 continue 690 691 try: 692 await self.items_service.Publish( 693 items_pb2.PublishRequest( 694 item=action_item 695 ) 696 ) 697 except grpc.RpcError as e: 698 # Если action уже опубликован 699 if "item cannot be published in this state" not in e.details(): 700 errors_list.append(f"Не удалось опубликовать действие {action_item.id}, {e.details()}") 701 except Exception as e: 702 errors_list.append(f"Не удалось опубликовать действие {action_item.id}, {e}") 703 704 return errors_list 705 706 async def __remove_actions(self, space_id: str, env_id: str) -> list[str]: 707 """ 708 Метод для удаления действий из определённого окружения пространства. Возвращает массив с ошибками 709 710 Arguments: 711 space_id (str): идентификатор пространства 712 env_id (str): идентификатор окружения 713 714 Returns: 715 list[str] 716 """ 717 718 errors_list = [] 719 for action in self.actions: 720 action_item = make_action_item(space_id, env_id, action) 721 722 try: 723 await self.items_service.Delete( 724 items_pb2.DeleteRequest( 725 item=items_pb2.Item( 726 id=action_item.id, 727 space_id=space_id, 728 env_id=env_id, 729 collection_id=action_item.collection_id, 730 ), 731 options=items_pb2.DeleteOptions( 732 update_attrs=True, 733 erase=True 734 ) 735 ) 736 ) 737 except grpc.RpcError as e: 738 # Отсутствие действия это не ошибка 739 if "not found" not in e.details(): 740 errors_list.append(f"Не удалось удалить действие {action.get('id', 'n/a')}, {e.details()}") 741 except Exception as e: 742 errors_list.append(f"Не удалось удалить действие - {e}") 743 744 return errors_list 745 746 async def __check_items(self, space_id: str, env_id: str) -> list[str]: 747 """ 748 Метод для item'ов действий из определённого окружения пространства. Возвращает массив с ошибками 749 750 Arguments: 751 space_id (str): идентификатор пространства 752 env_id (str): идентификатор окружения 753 754 Returns: 755 list[str] 756 """ 757 758 errors_list: list[str] = [] 759 wrapper = PerxisItemsWrapper( 760 self.items_service, 761 ) 762 763 for item in self.items: 764 all_rules_satisfied = await item.all_rules_is_satisfied(space_id, env_id) 765 766 if not all_rules_satisfied: 767 continue 768 769 try: 770 message = await wrapper.find( 771 collection_id=item.collection_id, 772 env_id=env_id, 773 space_id=space_id, 774 limit=1, 775 offset=0, 776 fields=["id"], 777 filters=[f"{item.identifier_field} == '{item.identifier}'"] 778 ) 779 780 if not message.total: 781 errors_list.append(f"Item {item.identifier} не найден") 782 except Exception as e: 783 errors_list.append( 784 f"Не удалось проверить item {item.identifier} " 785 f"коллекции {item.collection_id} - {e}" 786 ) 787 788 return errors_list 789 790 async def __update_items(self, space_id: str, env_id: str) -> list[str]: 791 """ 792 Метод для создания / обновления item'ов из определённого окружения пространства. Возвращает массив с ошибками 793 794 Arguments: 795 space_id (str): идентификатор пространства 796 env_id (str): идентификатор окружения 797 798 Returns: 799 list[str] 800 """ 801 802 errors_list: list[str] = [] 803 wrapper = PerxisItemsWrapper( 804 self.items_service, 805 ) 806 807 for item in self.items: 808 all_rules_satisfied = await item.all_rules_is_satisfied(space_id, env_id) 809 810 if not all_rules_satisfied: 811 continue 812 813 try: 814 message = await wrapper.find( 815 collection_id=item.collection_id, 816 env_id=env_id, 817 space_id=space_id, 818 limit=1, 819 offset=0, 820 filters=[f"{item.identifier_field} == '{item.identifier}'"] 821 ) 822 823 if message.items: 824 item_in_perxis = message.items[0] 825 else: 826 item_in_perxis = None 827 except Exception as e: 828 if hasattr(e, "details") and "not found" in e.details(): 829 is_error = False 830 else: 831 is_error = True 832 833 if is_error: 834 errors_list.append( 835 f"Не удалось получить item {item.identifier} " 836 f"коллекции {item.collection_id} - {e}" 837 ) 838 839 continue 840 841 try: 842 if item_in_perxis: 843 # Если установлен запрет на изменение item'ов 844 if not item.with_update: 845 continue 846 847 # Для того чтобы не затереть изменения в perxis 848 # Нужно смержить данные. Логика работы: 849 # 1. Данные которые указаны в `data` в расширении - в приоритете, они замещают то что в perxis 850 # 2. Данные которые есть в perxis но нет в расширении - дополняются 851 await wrapper.update( 852 collection_id=item.collection_id, 853 item_id=item_in_perxis.id, 854 space_id=space_id, 855 env_id=env_id, 856 data=item.merge_data(MessageToDict(item_in_perxis)["data"]), 857 ) 858 else: 859 message = await wrapper.create( 860 collection_id=item.collection_id, 861 space_id=space_id, 862 env_id=env_id, 863 data=item.struct, 864 ) 865 866 item_in_perxis = message.created 867 868 await wrapper.publish( 869 item_id=item_in_perxis.id, 870 collection_id=item.collection_id, 871 space_id=space_id, 872 env_id=env_id 873 ) 874 except Exception as e: 875 errors_list.append( 876 f"Не удалось записать item {item.identifier} " 877 f"коллекции {item.collection_id} - {e}" 878 ) 879 880 continue 881 882 return errors_list 883 884 async def __remove_items(self, space_id: str, env_id: str) -> list[str]: 885 """ 886 Метод для удаления item'ов из определённого окружения пространства. Возвращает массив с ошибками 887 888 Arguments: 889 space_id (str): идентификатор пространства 890 env_id (str): идентификатор окружения 891 892 Returns: 893 list[str] 894 """ 895 896 errors_list: list[str] = [] 897 wrapper = PerxisItemsWrapper( 898 self.items_service, 899 ) 900 901 for item in self.items: 902 all_rules_satisfied = await item.all_rules_is_satisfied(space_id, env_id) 903 904 if not all_rules_satisfied: 905 continue 906 907 # Если установлен запрет на удаление item'ов 908 if not item.with_delete: 909 continue 910 911 try: 912 message = await wrapper.find( 913 collection_id=item.collection_id, 914 env_id=env_id, 915 space_id=space_id, 916 limit=1, 917 offset=0, 918 fields=["id"], 919 filters=[f"{item.identifier_field} == '{item.identifier}'"] 920 ) 921 922 if message.items: 923 await wrapper.delete( 924 item_id=message.items[0].id, 925 collection_id=message.items[0].collection_id, 926 space_id=space_id, 927 env_id=env_id, 928 ) 929 except Exception as e: 930 if hasattr(e, "details") and "not found" in e.details(): 931 is_error = False 932 else: 933 is_error = True 934 935 if is_error: 936 errors_list.append( 937 f"Не удалось удалить item {item.identifier} " 938 f"коллекции {item.collection_id} - {e}" 939 ) 940 941 return errors_list 942 943 async def __update_view_role(self, space_id: str, env_id: str, mode: str = "add") -> list[str]: 944 """ 945 Метод для создания / обновления роли view из определённого окружения пространства. Возвращает массив с ошибками 946 947 Arguments: 948 space_id (str): идентификатор пространства 949 env_id (str): идентификатор окружения 950 951 Returns: 952 list[str] 953 """ 954 955 errors = [] 956 957 # Нужны только относящиеся к синхронизации элементы 958 items_for_view_role = [ 959 item 960 for item 961 in self.items 962 if (isinstance(item, SyncPolicyItem) or item.collection_id == "hoop_item_sync_policies") 963 and item.data["export_view"] 964 and await item.all_rules_is_satisfied(space_id=space_id, env_id=env_id) 965 ] 966 967 if not items_for_view_role: 968 return errors 969 970 try: 971 message = await self.roles_service.Get( 972 roles_pb2.GetRequest( 973 space_id=space_id, 974 role_id="view" 975 ) 976 ) 977 978 role = message.role 979 except grpc.RpcError as e: 980 if "not found" not in e.details(): 981 errors.append(f"Не удалось получить роль view, {e.details()}") 982 983 role = None 984 985 if not role: 986 try: 987 message = await self.roles_service.Create( 988 roles_pb2.CreateRequest( 989 role=roles_pb2.Role( 990 id="view", 991 space_id=space_id, 992 description="Роль для view коллекций", 993 rules=[], 994 environments=["*"], 995 allow_management=False, 996 ) 997 ), 998 ) 999 1000 role = message.created 1001 except grpc.RpcError as e: 1002 errors.append(f"Не удалось создать роль view, {e.details()}") 1003 1004 return errors 1005 1006 # Произвести мерж правил доступа 1007 for item in items_for_view_role: 1008 actions_from_item = [] 1009 1010 if not item.data["deny_read"]: 1011 actions_from_item.append(common_pb2.READ) 1012 1013 if not item.data["deny_create"]: 1014 actions_from_item.append(common_pb2.CREATE) 1015 1016 if not item.data["deny_publish"]: 1017 actions_from_item.append(common_pb2.UPDATE) 1018 1019 if not item.data["deny_delete"]: 1020 actions_from_item.append(common_pb2.DELETE) 1021 1022 rule_was_found = False 1023 for rule in role.rules: 1024 if rule.collection_id != item.data["collection"]: 1025 continue 1026 1027 rule_was_found = True 1028 1029 if mode == "add": 1030 modified_actions = list(set(list(rule.actions) + actions_from_item)) 1031 else: 1032 modified_actions = [ 1033 action 1034 for action 1035 in rule.actions 1036 if action not in actions_from_item 1037 ] 1038 1039 rule.actions[:] = modified_actions 1040 1041 # Если правила для коллекции нет и при этом доступы нужно __добавлять__ 1042 if not rule_was_found and mode == "add": 1043 role.rules.append( 1044 common_pb2.Rule( 1045 collection_id=item.data["collection"], 1046 actions=actions_from_item, 1047 ) 1048 ) 1049 1050 try: 1051 await self.roles_service.Update( 1052 roles_pb2.UpdateRequest( 1053 role=role 1054 ) 1055 ) 1056 except grpc.RpcError as e: 1057 errors.append(f"Не удалось обновить роль view, {e.details()}") 1058 1059 return errors 1060 1061 1062 async def install(self, space_id: str, env_id: str, use_force: bool) -> list[str]: 1063 """ 1064 Метод установки расширения. 1065 TODO разобраться нужны ли отдельные методы `install` и `update` 1066 TODO разобраться с аргументом `use_force` 1067 1068 Arguments: 1069 space_id (str): идентификатор пространства 1070 env_id (str): идентификатор окружения 1071 use_force (bool): флаг не используется 1072 1073 Returns: 1074 list[str] 1075 """ 1076 1077 errors = [] 1078 1079 try: 1080 errors += await self.__update_collections(space_id, env_id) 1081 errors += await self.__update_roles(space_id, env_id) 1082 errors += await self.__update_clients(space_id) 1083 errors += await self.__update_actions(space_id, env_id) 1084 errors += await self.__update_items(space_id, env_id) 1085 errors += await self.__update_view_role(space_id, env_id) 1086 except Exception as e: 1087 logger.exception(e) 1088 errors.append(f"Во время установки было необработанное исключение - {e}") 1089 1090 return errors 1091 1092 async def update(self, space_id: str, env_id: str, use_force: bool) -> list[str]: 1093 """ 1094 Метод установки расширения. 1095 TODO разобраться нужны ли отдельные методы `install` и `update` 1096 TODO разобраться с аргументом `use_force` 1097 1098 Arguments: 1099 space_id (str): идентификатор пространства 1100 env_id (str): идентификатор окружения 1101 use_force (bool): флаг не используется 1102 1103 Returns: 1104 list[str] 1105 """ 1106 1107 errors = [] 1108 1109 try: 1110 errors += await self.__update_collections(space_id, env_id) 1111 errors += await self.__update_roles(space_id, env_id) 1112 errors += await self.__update_clients(space_id) 1113 errors += await self.__update_actions(space_id, env_id) 1114 errors += await self.__update_items(space_id, env_id) 1115 errors += await self.__update_view_role(space_id, env_id) 1116 except Exception as e: 1117 logger.exception(e) 1118 errors.append(f"Во время обновления было необработанное исключение - {e}") 1119 1120 return errors 1121 1122 async def check(self, space_id: str, env_id: str) -> list[str]: 1123 """ 1124 Метод проверки расширения. 1125 1126 Arguments: 1127 space_id (str): идентификатор пространства 1128 env_id (str): идентификатор окружения 1129 1130 Returns: 1131 list[str] 1132 """ 1133 1134 errors = [] 1135 1136 try: 1137 errors += await self.__check_collections(space_id, env_id) 1138 errors += await self.__check_roles(space_id) 1139 errors += await self.__check_clients(space_id) 1140 errors += await self.__check_actions(space_id, env_id) 1141 errors += await self.__check_items(space_id, env_id) 1142 except Exception as e: 1143 logger.exception(e) 1144 errors.append(f"Во время проверки было необработанное исключение - {e}") 1145 1146 return errors 1147 1148 async def uninstall(self, space_id: str, env_id: str, use_remove: bool) -> list[str]: 1149 """ 1150 Метод удаления расширения. 1151 1152 Arguments: 1153 space_id (str): идентификатор пространства 1154 env_id (str): идентификатор окружения 1155 use_remove (str): удалять данные расширения 1156 1157 Returns: 1158 list[str] 1159 """ 1160 1161 errors = [] 1162 if use_remove: 1163 try: 1164 errors += await self.__remove_collections(space_id, env_id) 1165 errors += await self.__remove_clients(space_id) 1166 errors += await self.__remove_roles(space_id) 1167 errors += await self.__remove_actions(space_id, env_id) 1168 errors += await self.__remove_items(space_id, env_id) 1169 errors += await self.__update_view_role(space_id, env_id, "remove") 1170 except Exception as e: 1171 logger.exception(e) 1172 errors.append(f"Во время удаления данных было необработанное исключение - {e}") 1173 1174 return errors
logger =
<Logger perxis.extensions.extension_setup (WARNING)>
class
ExtensionSetup:
32class ExtensionSetup: 33 """ 34 Attributes: 35 collections_service (collections_pb2_grpc.CollectionsStub): ссылка на сервис коллекций 36 environments_service (environments_pb2_grpc.EnvironmentsStub): ссылка на сервис окружений 37 roles_service (roles_pb2_grpc.RolesStub): ссылка на сервис ролей 38 clients_service (clients_pb2_grpc.ClientsStub): ссылка на сервис клиентов 39 items_service (items_pb2_grpc.ItemsStub): ссылка на сервис item'ов 40 collections (list[collections_pb2.Collection]): список коллекций расширения 41 clients (list[clients_pb2.Client]): список клиентов расширения 42 roles (list[roles_pb2.Role]): список ролей расширения 43 items (list[AbstractItem]): список item'ов расширения 44 __max_attempts_count (int): макс. кол-во попыток записи 45 __sleep_time (int): время ожидания перед попыткой 46 47 """ 48 49 def __init__( 50 self, 51 collections_service: collections_pb2_grpc.CollectionsStub, 52 environments_service: environments_pb2_grpc.EnvironmentsStub, 53 roles_service: roles_pb2_grpc.RolesStub, 54 clients_service: clients_pb2_grpc.ClientsStub, 55 items_service: items_pb2_grpc.ItemsStub, 56 ): 57 self.collections = [] 58 self.clients = [] 59 self.roles = [] 60 self.actions = [] 61 self.items = [] 62 63 self.roles_service = roles_service 64 self.clients_service = clients_service 65 self.collections_service = collections_service 66 self.environments_service = environments_service 67 self.items_service = items_service 68 69 self.__max_attempts_count = 5 70 self.__sleep_time = 1 71 72 def add_action(self, action: dict): 73 self.actions.append(action) 74 75 def set_actions(self, actions: list[dict]): 76 self.actions = actions 77 78 def add_collection(self, collection: collections_pb2.Collection): 79 self.collections.append(collection) 80 81 def set_collections(self, collections: list[collections_pb2.Collection]): 82 self.collections = collections 83 84 def add_role(self, role: roles_pb2.Role): 85 self.roles.append(role) 86 87 def set_roles(self, roles: list[roles_pb2.Role]): 88 self.roles = roles 89 90 def add_client(self, client: clients_pb2.Client): 91 self.clients.append(client) 92 93 def set_clients(self, clients: list[clients_pb2.Client]): 94 self.clients = clients 95 96 def set_items(self, items: list[AbstractItem]): 97 self.items = items 98 99 # Работа с ролями 100 async def __remove_roles(self, space_id: str) -> list[str]: 101 """ 102 Метод для удаления ролей из пространства. Возвращает массив с ошибками 103 104 Arguments: 105 space_id (str): идентификатор пространства 106 107 Returns: 108 list[str] 109 """ 110 111 errors_list: list[str] = [] 112 113 for role in self.roles: 114 try: 115 await self.roles_service.Delete( 116 roles_pb2.DeleteRequest( 117 space_id=space_id, role_id=role.id 118 ) 119 ) 120 except grpc.RpcError as e: 121 # Если роли не существует считать это ошибкой не надо 122 if "not found" not in e.details(): 123 errors_list.append(f"Не удалось удалить роль {role.id}, {e.details()}") 124 125 return errors_list 126 127 async def __check_roles(self, space_id: str) -> list[str]: 128 """ 129 Метод для проверки ролей из пространства. Возвращает массив с ошибками 130 131 Arguments: 132 space_id (str): идентификатор пространства 133 134 Returns: 135 list[str] 136 """ 137 138 errors_list = [] 139 140 for role in self.roles: 141 try: 142 await self.roles_service.Get( 143 roles_pb2.GetRequest(space_id=space_id, role_id=role.id) 144 ) 145 except grpc.RpcError as e: 146 errors_list.append(f"Не удалось получить роль {role.id}, ошибка {e.details()}") 147 148 return errors_list 149 150 async def __update_roles(self, space_id: str, env_id: str) -> list[str]: 151 """ 152 Метод для установки ролей в пространство. Возвращает массив с ошибками. Права доступа 153 которые были установлены в perxis вручную или другим расширением не затираются. 154 155 Arguments: 156 space_id (str): идентификатор пространства 157 env_id (str): идентификатор окружения 158 159 Returns: 160 list[str] 161 """ 162 163 errors_list = [] 164 165 for local_role in self.roles: 166 try: 167 get_response = await self.roles_service.Get( 168 roles_pb2.GetRequest( 169 space_id=space_id, 170 role_id=local_role.id 171 ) 172 ) 173 174 role = get_response.role 175 except grpc.RpcError as e: 176 if "not found" not in e.details(): 177 errors_list.append(f"Не удалось получить роль {local_role.id}, {e.details()}") 178 continue 179 180 role = None 181 182 cloned_role = copy.deepcopy(local_role) 183 cloned_role.space_id = space_id 184 185 if role: 186 if not role.environments: 187 role.environments[:] = [env_id] 188 elif env_id not in role.environments: 189 role.environments.append(env_id) 190 191 cloned_role.environments[:] = role.environments 192 193 # Произвести мерж правил доступа 194 for exist_rule in role.rules: 195 was_found = False 196 197 for local_rule in cloned_role.rules: 198 # Если запись с правами доступа на коллекцию найдена - правила нужно совместить 199 if local_rule.collection_id == exist_rule.collection_id: 200 was_found = True 201 202 local_rule.actions[:] = list(set(list(local_rule.actions) + list(exist_rule.actions))) 203 204 break 205 206 # Если правило для коллекций не было найдено - его нужно добавить 207 if not was_found: 208 cloned_role.rules.append(common_pb2.Rule( 209 collection_id=exist_rule.collection_id, 210 actions=list(exist_rule.actions) 211 )) 212 213 try: 214 await self.roles_service.Update( 215 roles_pb2.UpdateRequest( 216 role=cloned_role 217 ) 218 ) 219 except grpc.RpcError as e: 220 errors_list.append(f"Не удалось обновить роль {local_role.id}, {e.details()}") 221 else: 222 if env_id not in cloned_role.environments: 223 cloned_role.environments.append(env_id) 224 225 try: 226 cloned_role = copy.deepcopy(local_role) 227 cloned_role.space_id = space_id 228 229 response = await self.roles_service.Create( 230 roles_pb2.CreateRequest( 231 role=cloned_role 232 ), 233 ) 234 except grpc.RpcError as e: 235 # На этапе install считается что ролей __нет__. При install с указанием force роли предварительно 236 # удаляются 237 errors_list.append(f"Не удалось создать роль {local_role.id}, {e.details()}") 238 239 return errors_list 240 241 # Работа с клиентами 242 async def __check_clients(self, space_id: str) -> list[str]: 243 """ 244 Метод для проверки клиентов в пространстве. Возвращает массив с ошибками 245 246 Arguments: 247 space_id (str): идентификатор пространства 248 249 Returns: 250 list[str] 251 """ 252 253 errors_list = [] 254 255 for client in self.clients: 256 try: 257 await self.clients_service.Get( 258 clients_pb2.GetRequest(space_id=space_id, id=client.id) 259 ) 260 except grpc.RpcError as e: 261 errors_list.append(f"Не удалось получить клиент {client.id}, ошибка {e.details()}") 262 263 return errors_list 264 265 async def __remove_clients(self, space_id: str) -> list[str]: 266 """ 267 Метод для удаления клиентов из пространства. Возвращает массив с ошибками 268 269 Arguments: 270 space_id (str): идентификатор пространства 271 272 Returns: 273 list[str] 274 """ 275 276 errors_list: list[str] = [] 277 278 for client in self.clients: 279 try: 280 await self.clients_service.Delete( 281 clients_pb2.DeleteRequest( 282 space_id=space_id, id=client.id 283 ) 284 ) 285 except grpc.RpcError as e: 286 # Отсутствие клиента ошибкой не считается 287 if "not found" not in e.details(): 288 errors_list.append(f"Не удалось удалить клиент {client.id}, {e.details()}") 289 290 return errors_list 291 292 async def __update_clients(self, space_id: str) -> list[str]: 293 """ 294 Метод для создания / обновления клиентов в пространстве. Возвращает массив с ошибками. 295 Свойства oauth и api_key не затираются в случае если клиент уже был создан 296 297 Arguments: 298 space_id (str): идентификатор пространства 299 300 Returns: 301 list[str] 302 """ 303 304 errors_list = [] 305 306 for local_client in self.clients: 307 try: 308 # Перед обновлением клиента предварительно нужно получить текущую запись чтобы скопировать оттуда 309 # токены. Иначе после обновления расширения перестанут работать все приложения которые использовали 310 # токен клиента 311 get_response = await self.clients_service.Get( 312 clients_pb2.GetRequest( 313 space_id=space_id, 314 id=local_client.id 315 ) 316 ) 317 318 client = get_response.client 319 except grpc.RpcError as e: 320 if "not found" not in e.details(): 321 errors_list.append(f"Не удалось получить клиент {local_client.id}, {e.details()}") 322 continue 323 324 client = None 325 326 if client: 327 try: 328 # Нужно чтобы у клиента каждый раз не слетали данные токенов при переустановке 329 # свойства oauth, api_key и tls должны браться из __созданного__ клиента 330 new_client = clients_pb2.Client( 331 id=local_client.id, 332 space_id=space_id, 333 name=local_client.name, 334 description=local_client.description, 335 disabled=client.disabled, 336 role_id=local_client.role_id, 337 oauth=client.oauth or local_client.oauth, 338 api_key=client.api_key or local_client.api_key, 339 tls=client.tls 340 ) 341 342 await self.clients_service.Update( 343 clients_pb2.UpdateRequest( 344 client=new_client 345 ) 346 ) 347 except grpc.RpcError as e: 348 errors_list.append(f"Не удалось обновить клиент {local_client.id}, {e.details()}") 349 else: 350 try: 351 cloned_client = copy.deepcopy(local_client) 352 cloned_client.space_id = space_id 353 354 await self.clients_service.Create( 355 clients_pb2.CreateRequest( 356 client=cloned_client 357 ), 358 ) 359 except grpc.RpcError as e: 360 errors_list.append(f"Не удалось создать клиент {local_client.id}, {e.details()}") 361 362 return errors_list 363 364 # Работа с коллекциями 365 async def __remove_collections(self, space_id: str, env_id: str) -> list[str]: 366 """ 367 Метод для удаления коллекций из определённого окружения пространства. Возвращает массив с ошибками 368 369 Arguments: 370 space_id (str): идентификатор пространства 371 env_id (str): идентификатор окружения 372 373 Returns: 374 list[str] 375 """ 376 377 errors_list: list[str] = [] 378 379 for collection in self.collections: 380 try: 381 await self.collections_service.Delete( 382 collections_pb2.DeleteRequest( 383 space_id=space_id, env_id=env_id, collection_id=collection.id 384 ) 385 ) 386 except grpc.RpcError as e: 387 # Отсутствие коллекции это не ошибка 388 if "not found" not in e.details(): 389 errors_list.append(f"Не удалось удалить коллекцию {collection.id}, {e.details()}") 390 391 return errors_list 392 393 async def __check_collections(self, space_id: str, env_id: str) -> list[str]: 394 """ 395 Метод для проверки коллекций из определённого окружения пространства. Возвращает массив с ошибками 396 397 Arguments: 398 space_id (str): идентификатор пространства 399 env_id (str): идентификатор окружения 400 401 Returns: 402 list[str] 403 """ 404 405 errors_list = [] 406 407 for collection in self.collections: 408 try: 409 await self.collections_service.Get( 410 collections_pb2.GetRequest(space_id=space_id, env_id=env_id, collection_id=collection.id) 411 ) 412 except grpc.RpcError as e: 413 errors_list.append(f"Не удалось получить коллекцию {collection.id}, ошибка {e.details()}") 414 415 return errors_list 416 417 async def __update_collections(self, space_id: str, env_id: str) -> list[str]: 418 """ 419 Метод __обновления__ коллекций. Миграция окружения требуется 420 только в случае если одна или несколько схем коллекций изменялись. Алгоритм работы: 421 1. Получить фактически существующую коллекцию из БД 422 2. Обновить её в perxis 423 3. Сравнить схему коллекции в расширении и в perxis 424 4. Если схема изменена - обновить схему в perxis 425 5. Если обновлялась хотя бы одна схема в perxis - запустить миграцию окружения 426 427 Arguments: 428 space_id (str): идентификатор пространства 429 env_id (str): идентификатор окружения 430 431 Returns: 432 list[str] 433 """ 434 435 need_to_migrate_environment = False 436 errors_list = [] 437 438 for local_collection in self.collections: 439 try: 440 # Необходимо получить текущую версию коллекции для того чтобы сравнить схемы 441 get_response = await self.collections_service.Get( 442 collections_pb2.GetRequest(space_id=space_id, env_id=env_id, collection_id=local_collection.id) 443 ) 444 445 collection = get_response.collection 446 except grpc.RpcError as e: 447 collection = None 448 449 cloned_collection = copy.deepcopy(local_collection) 450 cloned_collection.space_id = space_id 451 cloned_collection.env_id = env_id 452 453 # Коллекция может быть не найдена в случае если она добавлена в новой версии 454 if not collection: 455 try: 456 create_response = await self.collections_service.Create( 457 collections_pb2.CreateRequest(collection=cloned_collection) 458 ) 459 460 collection = create_response.created 461 except grpc.RpcError as e: 462 errors_list.append(f"Не удалось создать коллекцию {local_collection.id}, {e.details()}") 463 464 continue 465 else: 466 # если view-коллекция то не устанавливаем схему 467 if MessageToDict(collection).get("view"): 468 continue 469 try: 470 cloned_collection = copy.deepcopy(local_collection) 471 cloned_collection.space_id = space_id 472 cloned_collection.env_id = env_id 473 474 await self.collections_service.Update( 475 collections_pb2.UpdateRequest(collection=cloned_collection) 476 ) 477 except grpc.RpcError as e: 478 errors_list.append(f"Не удалось обновить коллекцию {local_collection.id}, {e.details()}") 479 480 continue 481 482 diff = DeepDiff( 483 json.loads(collection.schema or "{}"), 484 json.loads(local_collection.schema or "{}"), 485 ignore_numeric_type_changes=True, 486 exclude_paths=["root['loaded']"] 487 ) 488 if diff: 489 need_to_migrate_environment = True 490 491 set_schema_error_message = await self.__set_collection_schema( 492 space_id, env_id, local_collection.id, local_collection.schema 493 ) 494 if set_schema_error_message: 495 errors_list.append(set_schema_error_message) 496 497 if need_to_migrate_environment: 498 migrate_environment_error_message = await self.__migrate_environment(space_id, env_id) 499 if migrate_environment_error_message: 500 errors_list.append(migrate_environment_error_message) 501 502 return errors_list 503 504 async def __migrate_environment(self, space_id: str, env_id: str) -> typing.Optional[str]: 505 """ 506 Метод для миграции окружения. 507 Так как perxis может не сразу выставить коллекции / окружению статус ready операцию необходимо выполнять 508 с попытками 509 Arguments: 510 space_id (str): идентификатор пространства 511 env_id (str): идентификатор окружения 512 513 Returns: 514 str | None 515 """ 516 517 attempt = 0 518 is_ok = False 519 error_message = None 520 521 while attempt <= self.__max_attempts_count and not is_ok: 522 time.sleep(self.__sleep_time) 523 524 try: 525 await self.environments_service.Migrate(environments_pb2.MigrateRequest( 526 space_id=space_id, 527 env_id=env_id, 528 options=environments_pb2.MigrateOptions( 529 wait=True 530 ) 531 )) 532 533 is_ok = True 534 except grpc.RpcError as e: 535 # Если не удалось мигрировать окружение по любой причине кроме подготовки - это ошибка 536 if "is preparing" not in e.details(): 537 error_message = e.details() 538 539 # Для принудительного выхода из цикла 540 attempt = self.__max_attempts_count 541 542 attempt += 1 543 544 return error_message 545 546 async def __set_collection_schema(self, space_id: str, env_id: str, collection_id: str, schema: str) -> typing.Optional[str]: 547 """ 548 Метод для установки схемы коллекции в определённом окружении пространства 549 Так как perxis может не сразу выставить коллекции / окружению статус ready операцию необходимо выполнять 550 с попытками 551 Arguments: 552 space_id (str): идентификатор пространства 553 env_id (str): идентификатор окружения 554 collection_id (str): идентификатор коллекции 555 schema (str): схема коллекции в формате json 556 557 Returns: 558 str | None 559 """ 560 561 attempt = 0 562 is_ok = False 563 error_message = None 564 565 while attempt <= self.__max_attempts_count and not is_ok: 566 time.sleep(self.__sleep_time) 567 568 try: 569 await self.collections_service.SetSchema( 570 collections_pb2.SetSchemaRequest( 571 space_id=space_id, 572 env_id=env_id, 573 collection_id=collection_id, 574 schema=schema 575 ) 576 ) 577 578 is_ok = True 579 except grpc.RpcError as e: 580 # Если не удалось установить схему по любой причине кроме подготовки - это ошибка 581 if "is preparing" not in e.details(): 582 error_message = e.details() 583 584 # Для принудительного выхода из цикла 585 attempt = self.__max_attempts_count 586 587 attempt += 1 588 589 return error_message 590 591 # Работа с действиями 592 async def __check_actions(self, space_id: str, env_id: str) -> list[str]: 593 """ 594 Метод для проверки действий из определённого окружения пространства. Возвращает массив с ошибками 595 596 Arguments: 597 space_id (str): идентификатор пространства 598 env_id (str): идентификатор окружения 599 600 Returns: 601 list[str] 602 """ 603 604 errors_list = [] 605 606 ids = [data["id"] for data in self.actions if data.get("id")] 607 608 items = [] 609 610 try: 611 result = await self.items_service.Find( 612 items_pb2.FindRequest( 613 space_id=space_id, 614 env_id=env_id, 615 collection_id=ACTIONS_COLLECTION_ID, 616 filter=items_pb2.Filter(q=[f"id in {ids}"]), 617 options=items_pb2.FindOptions( 618 options=common_pb2.FindOptions( 619 page_num=0, page_size=len(ids) 620 ) 621 ), 622 ) 623 ) 624 625 items = result.items 626 except grpc.RpcError as e: 627 errors_list.append(f"Не удалось получить данные о действиях, ошибка {e.details()}") 628 finally: 629 found_ids = [item.id for item in items] 630 631 for action_id in found_ids: 632 if action_id not in ids: 633 errors_list.append(f"Действие {action_id} не найдено") 634 635 return errors_list 636 637 async def __update_actions(self, space_id: str, env_id: str) -> list[str]: 638 """ 639 Метод для создания / обновления действий из определённого окружения пространства. Возвращает массив с ошибками 640 641 Arguments: 642 space_id (str): идентификатор пространства 643 env_id (str): идентификатор окружения 644 645 Returns: 646 list[str] 647 """ 648 649 errors_list = [] 650 not_found = False 651 652 for action in self.actions: 653 action_item = make_action_item(space_id, env_id, action) 654 655 try: 656 await self.items_service.Update( 657 items_pb2.UpdateRequest( 658 item=items_pb2.Item( 659 id=action_item.id, 660 space_id=space_id, 661 env_id=env_id, 662 collection_id=ACTIONS_COLLECTION_ID, 663 data=action_item.data, 664 ) 665 ) 666 ) 667 except grpc.RpcError as e: 668 if "not found" not in e.details(): 669 errors_list.append(f"Не удалось обновить действие {action_item.id}, {e.details()}") 670 continue 671 672 not_found = True 673 except Exception as e: 674 errors_list.append(f"Не удалось обновить действие {action_item.id}, {e}") 675 676 continue 677 678 if not_found: 679 try: 680 await self.items_service.Create( 681 items_pb2.CreateRequest( 682 item=action_item 683 ) 684 ) 685 except grpc.RpcError as e: 686 errors_list.append(f"Не удалось создать действие {action_item.id}, {e.details()}") 687 except Exception as e: 688 errors_list.append(f"Не удалось создать действие {action_item.id}, {e}") 689 690 continue 691 692 try: 693 await self.items_service.Publish( 694 items_pb2.PublishRequest( 695 item=action_item 696 ) 697 ) 698 except grpc.RpcError as e: 699 # Если action уже опубликован 700 if "item cannot be published in this state" not in e.details(): 701 errors_list.append(f"Не удалось опубликовать действие {action_item.id}, {e.details()}") 702 except Exception as e: 703 errors_list.append(f"Не удалось опубликовать действие {action_item.id}, {e}") 704 705 return errors_list 706 707 async def __remove_actions(self, space_id: str, env_id: str) -> list[str]: 708 """ 709 Метод для удаления действий из определённого окружения пространства. Возвращает массив с ошибками 710 711 Arguments: 712 space_id (str): идентификатор пространства 713 env_id (str): идентификатор окружения 714 715 Returns: 716 list[str] 717 """ 718 719 errors_list = [] 720 for action in self.actions: 721 action_item = make_action_item(space_id, env_id, action) 722 723 try: 724 await self.items_service.Delete( 725 items_pb2.DeleteRequest( 726 item=items_pb2.Item( 727 id=action_item.id, 728 space_id=space_id, 729 env_id=env_id, 730 collection_id=action_item.collection_id, 731 ), 732 options=items_pb2.DeleteOptions( 733 update_attrs=True, 734 erase=True 735 ) 736 ) 737 ) 738 except grpc.RpcError as e: 739 # Отсутствие действия это не ошибка 740 if "not found" not in e.details(): 741 errors_list.append(f"Не удалось удалить действие {action.get('id', 'n/a')}, {e.details()}") 742 except Exception as e: 743 errors_list.append(f"Не удалось удалить действие - {e}") 744 745 return errors_list 746 747 async def __check_items(self, space_id: str, env_id: str) -> list[str]: 748 """ 749 Метод для item'ов действий из определённого окружения пространства. Возвращает массив с ошибками 750 751 Arguments: 752 space_id (str): идентификатор пространства 753 env_id (str): идентификатор окружения 754 755 Returns: 756 list[str] 757 """ 758 759 errors_list: list[str] = [] 760 wrapper = PerxisItemsWrapper( 761 self.items_service, 762 ) 763 764 for item in self.items: 765 all_rules_satisfied = await item.all_rules_is_satisfied(space_id, env_id) 766 767 if not all_rules_satisfied: 768 continue 769 770 try: 771 message = await wrapper.find( 772 collection_id=item.collection_id, 773 env_id=env_id, 774 space_id=space_id, 775 limit=1, 776 offset=0, 777 fields=["id"], 778 filters=[f"{item.identifier_field} == '{item.identifier}'"] 779 ) 780 781 if not message.total: 782 errors_list.append(f"Item {item.identifier} не найден") 783 except Exception as e: 784 errors_list.append( 785 f"Не удалось проверить item {item.identifier} " 786 f"коллекции {item.collection_id} - {e}" 787 ) 788 789 return errors_list 790 791 async def __update_items(self, space_id: str, env_id: str) -> list[str]: 792 """ 793 Метод для создания / обновления item'ов из определённого окружения пространства. Возвращает массив с ошибками 794 795 Arguments: 796 space_id (str): идентификатор пространства 797 env_id (str): идентификатор окружения 798 799 Returns: 800 list[str] 801 """ 802 803 errors_list: list[str] = [] 804 wrapper = PerxisItemsWrapper( 805 self.items_service, 806 ) 807 808 for item in self.items: 809 all_rules_satisfied = await item.all_rules_is_satisfied(space_id, env_id) 810 811 if not all_rules_satisfied: 812 continue 813 814 try: 815 message = await wrapper.find( 816 collection_id=item.collection_id, 817 env_id=env_id, 818 space_id=space_id, 819 limit=1, 820 offset=0, 821 filters=[f"{item.identifier_field} == '{item.identifier}'"] 822 ) 823 824 if message.items: 825 item_in_perxis = message.items[0] 826 else: 827 item_in_perxis = None 828 except Exception as e: 829 if hasattr(e, "details") and "not found" in e.details(): 830 is_error = False 831 else: 832 is_error = True 833 834 if is_error: 835 errors_list.append( 836 f"Не удалось получить item {item.identifier} " 837 f"коллекции {item.collection_id} - {e}" 838 ) 839 840 continue 841 842 try: 843 if item_in_perxis: 844 # Если установлен запрет на изменение item'ов 845 if not item.with_update: 846 continue 847 848 # Для того чтобы не затереть изменения в perxis 849 # Нужно смержить данные. Логика работы: 850 # 1. Данные которые указаны в `data` в расширении - в приоритете, они замещают то что в perxis 851 # 2. Данные которые есть в perxis но нет в расширении - дополняются 852 await wrapper.update( 853 collection_id=item.collection_id, 854 item_id=item_in_perxis.id, 855 space_id=space_id, 856 env_id=env_id, 857 data=item.merge_data(MessageToDict(item_in_perxis)["data"]), 858 ) 859 else: 860 message = await wrapper.create( 861 collection_id=item.collection_id, 862 space_id=space_id, 863 env_id=env_id, 864 data=item.struct, 865 ) 866 867 item_in_perxis = message.created 868 869 await wrapper.publish( 870 item_id=item_in_perxis.id, 871 collection_id=item.collection_id, 872 space_id=space_id, 873 env_id=env_id 874 ) 875 except Exception as e: 876 errors_list.append( 877 f"Не удалось записать item {item.identifier} " 878 f"коллекции {item.collection_id} - {e}" 879 ) 880 881 continue 882 883 return errors_list 884 885 async def __remove_items(self, space_id: str, env_id: str) -> list[str]: 886 """ 887 Метод для удаления item'ов из определённого окружения пространства. Возвращает массив с ошибками 888 889 Arguments: 890 space_id (str): идентификатор пространства 891 env_id (str): идентификатор окружения 892 893 Returns: 894 list[str] 895 """ 896 897 errors_list: list[str] = [] 898 wrapper = PerxisItemsWrapper( 899 self.items_service, 900 ) 901 902 for item in self.items: 903 all_rules_satisfied = await item.all_rules_is_satisfied(space_id, env_id) 904 905 if not all_rules_satisfied: 906 continue 907 908 # Если установлен запрет на удаление item'ов 909 if not item.with_delete: 910 continue 911 912 try: 913 message = await wrapper.find( 914 collection_id=item.collection_id, 915 env_id=env_id, 916 space_id=space_id, 917 limit=1, 918 offset=0, 919 fields=["id"], 920 filters=[f"{item.identifier_field} == '{item.identifier}'"] 921 ) 922 923 if message.items: 924 await wrapper.delete( 925 item_id=message.items[0].id, 926 collection_id=message.items[0].collection_id, 927 space_id=space_id, 928 env_id=env_id, 929 ) 930 except Exception as e: 931 if hasattr(e, "details") and "not found" in e.details(): 932 is_error = False 933 else: 934 is_error = True 935 936 if is_error: 937 errors_list.append( 938 f"Не удалось удалить item {item.identifier} " 939 f"коллекции {item.collection_id} - {e}" 940 ) 941 942 return errors_list 943 944 async def __update_view_role(self, space_id: str, env_id: str, mode: str = "add") -> list[str]: 945 """ 946 Метод для создания / обновления роли view из определённого окружения пространства. Возвращает массив с ошибками 947 948 Arguments: 949 space_id (str): идентификатор пространства 950 env_id (str): идентификатор окружения 951 952 Returns: 953 list[str] 954 """ 955 956 errors = [] 957 958 # Нужны только относящиеся к синхронизации элементы 959 items_for_view_role = [ 960 item 961 for item 962 in self.items 963 if (isinstance(item, SyncPolicyItem) or item.collection_id == "hoop_item_sync_policies") 964 and item.data["export_view"] 965 and await item.all_rules_is_satisfied(space_id=space_id, env_id=env_id) 966 ] 967 968 if not items_for_view_role: 969 return errors 970 971 try: 972 message = await self.roles_service.Get( 973 roles_pb2.GetRequest( 974 space_id=space_id, 975 role_id="view" 976 ) 977 ) 978 979 role = message.role 980 except grpc.RpcError as e: 981 if "not found" not in e.details(): 982 errors.append(f"Не удалось получить роль view, {e.details()}") 983 984 role = None 985 986 if not role: 987 try: 988 message = await self.roles_service.Create( 989 roles_pb2.CreateRequest( 990 role=roles_pb2.Role( 991 id="view", 992 space_id=space_id, 993 description="Роль для view коллекций", 994 rules=[], 995 environments=["*"], 996 allow_management=False, 997 ) 998 ), 999 ) 1000 1001 role = message.created 1002 except grpc.RpcError as e: 1003 errors.append(f"Не удалось создать роль view, {e.details()}") 1004 1005 return errors 1006 1007 # Произвести мерж правил доступа 1008 for item in items_for_view_role: 1009 actions_from_item = [] 1010 1011 if not item.data["deny_read"]: 1012 actions_from_item.append(common_pb2.READ) 1013 1014 if not item.data["deny_create"]: 1015 actions_from_item.append(common_pb2.CREATE) 1016 1017 if not item.data["deny_publish"]: 1018 actions_from_item.append(common_pb2.UPDATE) 1019 1020 if not item.data["deny_delete"]: 1021 actions_from_item.append(common_pb2.DELETE) 1022 1023 rule_was_found = False 1024 for rule in role.rules: 1025 if rule.collection_id != item.data["collection"]: 1026 continue 1027 1028 rule_was_found = True 1029 1030 if mode == "add": 1031 modified_actions = list(set(list(rule.actions) + actions_from_item)) 1032 else: 1033 modified_actions = [ 1034 action 1035 for action 1036 in rule.actions 1037 if action not in actions_from_item 1038 ] 1039 1040 rule.actions[:] = modified_actions 1041 1042 # Если правила для коллекции нет и при этом доступы нужно __добавлять__ 1043 if not rule_was_found and mode == "add": 1044 role.rules.append( 1045 common_pb2.Rule( 1046 collection_id=item.data["collection"], 1047 actions=actions_from_item, 1048 ) 1049 ) 1050 1051 try: 1052 await self.roles_service.Update( 1053 roles_pb2.UpdateRequest( 1054 role=role 1055 ) 1056 ) 1057 except grpc.RpcError as e: 1058 errors.append(f"Не удалось обновить роль view, {e.details()}") 1059 1060 return errors 1061 1062 1063 async def install(self, space_id: str, env_id: str, use_force: bool) -> list[str]: 1064 """ 1065 Метод установки расширения. 1066 TODO разобраться нужны ли отдельные методы `install` и `update` 1067 TODO разобраться с аргументом `use_force` 1068 1069 Arguments: 1070 space_id (str): идентификатор пространства 1071 env_id (str): идентификатор окружения 1072 use_force (bool): флаг не используется 1073 1074 Returns: 1075 list[str] 1076 """ 1077 1078 errors = [] 1079 1080 try: 1081 errors += await self.__update_collections(space_id, env_id) 1082 errors += await self.__update_roles(space_id, env_id) 1083 errors += await self.__update_clients(space_id) 1084 errors += await self.__update_actions(space_id, env_id) 1085 errors += await self.__update_items(space_id, env_id) 1086 errors += await self.__update_view_role(space_id, env_id) 1087 except Exception as e: 1088 logger.exception(e) 1089 errors.append(f"Во время установки было необработанное исключение - {e}") 1090 1091 return errors 1092 1093 async def update(self, space_id: str, env_id: str, use_force: bool) -> list[str]: 1094 """ 1095 Метод установки расширения. 1096 TODO разобраться нужны ли отдельные методы `install` и `update` 1097 TODO разобраться с аргументом `use_force` 1098 1099 Arguments: 1100 space_id (str): идентификатор пространства 1101 env_id (str): идентификатор окружения 1102 use_force (bool): флаг не используется 1103 1104 Returns: 1105 list[str] 1106 """ 1107 1108 errors = [] 1109 1110 try: 1111 errors += await self.__update_collections(space_id, env_id) 1112 errors += await self.__update_roles(space_id, env_id) 1113 errors += await self.__update_clients(space_id) 1114 errors += await self.__update_actions(space_id, env_id) 1115 errors += await self.__update_items(space_id, env_id) 1116 errors += await self.__update_view_role(space_id, env_id) 1117 except Exception as e: 1118 logger.exception(e) 1119 errors.append(f"Во время обновления было необработанное исключение - {e}") 1120 1121 return errors 1122 1123 async def check(self, space_id: str, env_id: str) -> list[str]: 1124 """ 1125 Метод проверки расширения. 1126 1127 Arguments: 1128 space_id (str): идентификатор пространства 1129 env_id (str): идентификатор окружения 1130 1131 Returns: 1132 list[str] 1133 """ 1134 1135 errors = [] 1136 1137 try: 1138 errors += await self.__check_collections(space_id, env_id) 1139 errors += await self.__check_roles(space_id) 1140 errors += await self.__check_clients(space_id) 1141 errors += await self.__check_actions(space_id, env_id) 1142 errors += await self.__check_items(space_id, env_id) 1143 except Exception as e: 1144 logger.exception(e) 1145 errors.append(f"Во время проверки было необработанное исключение - {e}") 1146 1147 return errors 1148 1149 async def uninstall(self, space_id: str, env_id: str, use_remove: bool) -> list[str]: 1150 """ 1151 Метод удаления расширения. 1152 1153 Arguments: 1154 space_id (str): идентификатор пространства 1155 env_id (str): идентификатор окружения 1156 use_remove (str): удалять данные расширения 1157 1158 Returns: 1159 list[str] 1160 """ 1161 1162 errors = [] 1163 if use_remove: 1164 try: 1165 errors += await self.__remove_collections(space_id, env_id) 1166 errors += await self.__remove_clients(space_id) 1167 errors += await self.__remove_roles(space_id) 1168 errors += await self.__remove_actions(space_id, env_id) 1169 errors += await self.__remove_items(space_id, env_id) 1170 errors += await self.__update_view_role(space_id, env_id, "remove") 1171 except Exception as e: 1172 logger.exception(e) 1173 errors.append(f"Во время удаления данных было необработанное исключение - {e}") 1174 1175 return errors
Attributes:
- collections_service (collections_pb2_grpc.CollectionsStub): ссылка на сервис коллекций
- environments_service (environments_pb2_grpc.EnvironmentsStub): ссылка на сервис окружений
- roles_service (roles_pb2_grpc.RolesStub): ссылка на сервис ролей
- clients_service (clients_pb2_grpc.ClientsStub): ссылка на сервис клиентов
- items_service (items_pb2_grpc.ItemsStub): ссылка на сервис item'ов
- collections (list[collections_pb2.Collection]): список коллекций расширения
- clients (list[clients_pb2.Client]): список клиентов расширения
- roles (list[roles_pb2.Role]): список ролей расширения
- items (list[AbstractItem]): список item'ов расширения
- __max_attempts_count (int): макс. кол-во попыток записи
- __sleep_time (int): время ожидания перед попыткой
ExtensionSetup( collections_service: perxis.collections.collections_pb2_grpc.CollectionsStub, environments_service: perxis.environments.environments_pb2_grpc.EnvironmentsStub, roles_service: perxis.roles.roles_pb2_grpc.RolesStub, clients_service: perxis.clients.clients_pb2_grpc.ClientsStub, items_service: perxis.items.items_pb2_grpc.ItemsStub)
49 def __init__( 50 self, 51 collections_service: collections_pb2_grpc.CollectionsStub, 52 environments_service: environments_pb2_grpc.EnvironmentsStub, 53 roles_service: roles_pb2_grpc.RolesStub, 54 clients_service: clients_pb2_grpc.ClientsStub, 55 items_service: items_pb2_grpc.ItemsStub, 56 ): 57 self.collections = [] 58 self.clients = [] 59 self.roles = [] 60 self.actions = [] 61 self.items = [] 62 63 self.roles_service = roles_service 64 self.clients_service = clients_service 65 self.collections_service = collections_service 66 self.environments_service = environments_service 67 self.items_service = items_service 68 69 self.__max_attempts_count = 5 70 self.__sleep_time = 1
async def
install(self, space_id: str, env_id: str, use_force: bool) -> list[str]:
1063 async def install(self, space_id: str, env_id: str, use_force: bool) -> list[str]: 1064 """ 1065 Метод установки расширения. 1066 TODO разобраться нужны ли отдельные методы `install` и `update` 1067 TODO разобраться с аргументом `use_force` 1068 1069 Arguments: 1070 space_id (str): идентификатор пространства 1071 env_id (str): идентификатор окружения 1072 use_force (bool): флаг не используется 1073 1074 Returns: 1075 list[str] 1076 """ 1077 1078 errors = [] 1079 1080 try: 1081 errors += await self.__update_collections(space_id, env_id) 1082 errors += await self.__update_roles(space_id, env_id) 1083 errors += await self.__update_clients(space_id) 1084 errors += await self.__update_actions(space_id, env_id) 1085 errors += await self.__update_items(space_id, env_id) 1086 errors += await self.__update_view_role(space_id, env_id) 1087 except Exception as e: 1088 logger.exception(e) 1089 errors.append(f"Во время установки было необработанное исключение - {e}") 1090 1091 return errors
async def
update(self, space_id: str, env_id: str, use_force: bool) -> list[str]:
1093 async def update(self, space_id: str, env_id: str, use_force: bool) -> list[str]: 1094 """ 1095 Метод установки расширения. 1096 TODO разобраться нужны ли отдельные методы `install` и `update` 1097 TODO разобраться с аргументом `use_force` 1098 1099 Arguments: 1100 space_id (str): идентификатор пространства 1101 env_id (str): идентификатор окружения 1102 use_force (bool): флаг не используется 1103 1104 Returns: 1105 list[str] 1106 """ 1107 1108 errors = [] 1109 1110 try: 1111 errors += await self.__update_collections(space_id, env_id) 1112 errors += await self.__update_roles(space_id, env_id) 1113 errors += await self.__update_clients(space_id) 1114 errors += await self.__update_actions(space_id, env_id) 1115 errors += await self.__update_items(space_id, env_id) 1116 errors += await self.__update_view_role(space_id, env_id) 1117 except Exception as e: 1118 logger.exception(e) 1119 errors.append(f"Во время обновления было необработанное исключение - {e}") 1120 1121 return errors
async def
check(self, space_id: str, env_id: str) -> list[str]:
1123 async def check(self, space_id: str, env_id: str) -> list[str]: 1124 """ 1125 Метод проверки расширения. 1126 1127 Arguments: 1128 space_id (str): идентификатор пространства 1129 env_id (str): идентификатор окружения 1130 1131 Returns: 1132 list[str] 1133 """ 1134 1135 errors = [] 1136 1137 try: 1138 errors += await self.__check_collections(space_id, env_id) 1139 errors += await self.__check_roles(space_id) 1140 errors += await self.__check_clients(space_id) 1141 errors += await self.__check_actions(space_id, env_id) 1142 errors += await self.__check_items(space_id, env_id) 1143 except Exception as e: 1144 logger.exception(e) 1145 errors.append(f"Во время проверки было необработанное исключение - {e}") 1146 1147 return errors
Метод проверки расширения.
Arguments:
- space_id (str): идентификатор пространства
- env_id (str): идентификатор окружения
Returns:
list[str]
async def
uninstall(self, space_id: str, env_id: str, use_remove: bool) -> list[str]:
1149 async def uninstall(self, space_id: str, env_id: str, use_remove: bool) -> list[str]: 1150 """ 1151 Метод удаления расширения. 1152 1153 Arguments: 1154 space_id (str): идентификатор пространства 1155 env_id (str): идентификатор окружения 1156 use_remove (str): удалять данные расширения 1157 1158 Returns: 1159 list[str] 1160 """ 1161 1162 errors = [] 1163 if use_remove: 1164 try: 1165 errors += await self.__remove_collections(space_id, env_id) 1166 errors += await self.__remove_clients(space_id) 1167 errors += await self.__remove_roles(space_id) 1168 errors += await self.__remove_actions(space_id, env_id) 1169 errors += await self.__remove_items(space_id, env_id) 1170 errors += await self.__update_view_role(space_id, env_id, "remove") 1171 except Exception as e: 1172 logger.exception(e) 1173 errors.append(f"Во время удаления данных было необработанное исключение - {e}") 1174 1175 return errors
Метод удаления расширения.
Arguments:
- space_id (str): идентификатор пространства
- env_id (str): идентификатор окружения
- use_remove (str): удалять данные расширения
Returns:
list[str]