diff --git a/apiserver/paasng/paasng/accessories/servicehub/manager.py b/apiserver/paasng/paasng/accessories/servicehub/manager.py index 4b08decaa4..ff11d25bfc 100644 --- a/apiserver/paasng/paasng/accessories/servicehub/manager.py +++ b/apiserver/paasng/paasng/accessories/servicehub/manager.py @@ -246,6 +246,25 @@ def get_env_vars( result.update(i.credentials) return result + def get_enabled_env_keys(self, engine_app: EngineApp) -> Dict[str, List[str]]: + """ + Get all provisioned services environment keys + + :param engine_app: EngineApp object + :return: Dictionary of service display names to list of their environment keys + """ + provisioned_rels = self.list_provisioned_rels(engine_app) + # 凭证的信息写入环境变量的增强服务才展示 + enabled_rels = [rel for rel in provisioned_rels if rel.db_obj.credentials_enabled] + + results = {} + for rel in enabled_rels: + service_name = rel.get_service().display_name + instance_credentials_keys = list(rel.get_instance().credentials.keys()) + results[service_name] = instance_credentials_keys + + return results + def get_attachment_by_engine_app(self, service: ServiceObj, engine_app: EngineApp): for mgr in self.mgr_instances: try: diff --git a/apiserver/paasng/paasng/accessories/servicehub/sharing.py b/apiserver/paasng/paasng/accessories/servicehub/sharing.py index 7ece9b2eeb..6805f68d11 100644 --- a/apiserver/paasng/paasng/accessories/servicehub/sharing.py +++ b/apiserver/paasng/paasng/accessories/servicehub/sharing.py @@ -18,7 +18,7 @@ """ """Shared service across modules""" import logging -from typing import Dict, Iterable, Optional, Sequence +from typing import Dict, Iterable, List, Optional, Sequence from django.core.exceptions import ObjectDoesNotExist @@ -138,6 +138,26 @@ def get_env_variables(self, env: ModuleEnvironment, filter_enabled: bool = False ret.update(env_variables) return ret + def get_enabled_env_keys(self, env: ModuleEnvironment) -> Dict[str, List[str]]: + """ + Retrieve all environment variable keys shared from other modules. + + :param env: ModuleEnvironment object that must belong to self.module + :return: Dictionary of service display names to their respective env keys list. + """ + if env.module != self.module: + raise RuntimeError("Invalid env object, must belong to self.module") + + result = {} + for referenced_info in self.list_all_shared_info(): + ref_env = referenced_info.ref_module.get_envs(env.environment) + ref_service = referenced_info.service + env_keys = mixed_service_mgr.get_env_vars(ref_env.engine_app, ref_service, True) + + result[ref_service.display_name] = list(env_keys.keys()) + + return result + def extract_shared_info(attachment: SharedServiceAttachment) -> Optional[SharedServiceInfo]: """Extract shared service infomation by attachment object diff --git a/apiserver/paasng/paasng/accessories/servicehub/urls.py b/apiserver/paasng/paasng/accessories/servicehub/urls.py index 8bf7e2478f..3877357d37 100644 --- a/apiserver/paasng/paasng/accessories/servicehub/urls.py +++ b/apiserver/paasng/paasng/accessories/servicehub/urls.py @@ -87,6 +87,11 @@ views.ModuleServiceAttachmentsViewSet.as_view({"get": "retrieve_info"}), name="api.modules.services.info", ), + re_path( + make_app_pattern("/services/config_var_keys/$", include_envs=False), + views.ModuleServiceAttachmentsViewSet.as_view({"get": "list_provisioned_env_keys"}), + name="api.services.list_provisioned_env_keys", + ), re_path( make_app_pattern(f"/services/{SERVICE_UUID}/specs$", include_envs=False), views.ModuleServicesViewSet.as_view({"get": "retrieve_specs"}), diff --git a/apiserver/paasng/paasng/accessories/servicehub/views.py b/apiserver/paasng/paasng/accessories/servicehub/views.py index 33dd09a9cc..ca613ae769 100644 --- a/apiserver/paasng/paasng/accessories/servicehub/views.py +++ b/apiserver/paasng/paasng/accessories/servicehub/views.py @@ -99,6 +99,20 @@ def retrieve_info(self, request, code, module_name): services_info[env.environment] = ServicesInfo.get_detail(env.engine_app)["services_info"] return Response(data=slzs.ModuleServiceInfoSLZ(services_info).data) + def list_provisioned_env_keys(self, request, code, module_name): + """获取已经生效的增强服务环境变量 KEY""" + module = self.get_module_via_path() + + # env_key_dict 内容示例: {"svc_name": ["key1", "key2"]} + env_key_dict: Dict[str, List[str]] = {} + for env in module.get_envs(): + env_key_dict = { + **env_key_dict, + **ServiceSharingManager(env.module).get_enabled_env_keys(env), + **mixed_service_mgr.get_enabled_env_keys(env.engine_app), + } + return Response(data=env_key_dict) + class ModuleServicesViewSet(viewsets.ViewSet, ApplicationCodeInPathMixin): """与蓝鲸应用模块相关的增强服务接口""" diff --git a/apiserver/paasng/tests/api/test_servicehub.py b/apiserver/paasng/tests/api/test_servicehub.py index 4b8c938e3e..934c442b8a 100644 --- a/apiserver/paasng/tests/api/test_servicehub.py +++ b/apiserver/paasng/tests/api/test_servicehub.py @@ -16,12 +16,14 @@ We undertake not to change the open source license (MIT license) applicable to the current version of the project delivered to anyone in the future. """ +import datetime from unittest import mock import pytest from django_dynamic_fixture import G from paasng.accessories.servicehub.models import RemoteServiceEngineAppAttachment +from paasng.accessories.servicehub.services import ServiceInstanceObj from paasng.accessories.services.models import Service pytestmark = pytest.mark.django_db @@ -38,6 +40,15 @@ def side_effect(*args, **kwargs): return side_effect + def create_mock_rel(self, service, credentials_enabled, create_time: "datetime.datetime", **credentials): + rel = mock.MagicMock() + rel.get_instance.return_value = ServiceInstanceObj( + uuid=service.uuid, credentials=credentials, config={}, create_time=create_time + ) + rel.get_service.return_value = service + rel.db_obj.credentials_enabled = credentials_enabled + return rel + @mock.patch("paasng.accessories.servicehub.views.mixed_service_mgr.get_or_404") @mock.patch("paasng.accessories.servicehub.views.mixed_service_mgr.get_attachment_by_engine_app") def test_list(self, get_attachment_by_engine_app, get_or_404, api_client, bk_app, bk_module): @@ -63,3 +74,24 @@ def test_update(self, get_attachment_by_engine_app, get_or_404, api_client, bk_a assert response.status_code == 200 assert response.data[0]["credentials_enabled"] is False assert response.data[1]["credentials_enabled"] is False + + @mock.patch("paasng.accessories.servicehub.manager.MixedServiceMgr.list_provisioned_rels") + def test_config_vars(self, list_provisioned_rels, api_client, bk_app, bk_module): + service = G(Service) + credentials_disabled_service = G(Service) + list_provisioned_rels.return_value = [ + self.create_mock_rel(service, True, datetime.datetime(2020, 1, 1), a=1, b=1), + # 增强服务环境变量不写入 + self.create_mock_rel(credentials_disabled_service, False, datetime.datetime(2020, 1, 1), c=1), + ] + + response = api_client.get( + f"/api/bkapps/applications/{bk_app.code}/modules/{bk_module.name}/services/config_var_keys/", + ) + assert response.status_code == 200 + # 返回的增强服务名称列表 + return_svc_names = list(response.data.keys()) + assert service.display_name in return_svc_names + assert set(response.data[service.display_name]) == {"a", "b"} + # 增强服务环境变量设置为不写入则不返回 + assert credentials_disabled_service.display_name not in return_svc_names