diff --git a/src/dashboard/apigateway/apigateway/apis/open/resource/serializers.py b/src/dashboard/apigateway/apigateway/apis/open/resource/serializers.py index e65bbc1e1..313af6659 100644 --- a/src/dashboard/apigateway/apigateway/apis/open/resource/serializers.py +++ b/src/dashboard/apigateway/apigateway/apis/open/resource/serializers.py @@ -29,6 +29,7 @@ class ResourceSyncOutputSLZ(serializers.Serializer): class ResourceImportInputSLZ(serializers.Serializer): gateway = serializers.HiddenField(default=CurrentGatewayDefault()) content = serializers.CharField(allow_blank=False, required=True, help_text="导入内容,yaml/json 格式字符串") + delete = serializers.BooleanField(required=False, default=False) class Meta: ref_name = "apis.open.resource.ResourceImportInputSLZ" diff --git a/src/dashboard/apigateway/apigateway/apis/open/resource/views.py b/src/dashboard/apigateway/apigateway/apis/open/resource/views.py index b8a24c9f1..670dabb4c 100644 --- a/src/dashboard/apigateway/apigateway/apis/open/resource/views.py +++ b/src/dashboard/apigateway/apigateway/apis/open/resource/views.py @@ -56,7 +56,11 @@ def post(self, request, *args, **kwargs): slz.is_valid(raise_exception=True) try: - openapi_manager = OpenAPIImportManager.load_from_content(request.gateway, slz.validated_data["content"]) + openapi_manager = OpenAPIImportManager.load_from_content( + request.gateway, + slz.validated_data["content"], + need_delete_unspecified_resources=slz.validated_data["delete"], + ) except Exception as err: raise serializers.ValidationError( {"content": _("导入内容为无效的 json/yaml 数据,{err}。").format(err=err)} @@ -77,6 +81,8 @@ def post(self, request, *args, **kwargs): gateway=request.gateway, resources=openapi_manager.get_resource_list(), username=request.user.username, + selected_resources=None, + need_delete_unspecified_resources=slz.validated_data["delete"], ) importer.import_resources() diff --git a/src/dashboard/apigateway/apigateway/biz/resource/importer/openapi.py b/src/dashboard/apigateway/apigateway/biz/resource/importer/openapi.py index 2ac4ea66f..5aead5a70 100644 --- a/src/dashboard/apigateway/apigateway/biz/resource/importer/openapi.py +++ b/src/dashboard/apigateway/apigateway/biz/resource/importer/openapi.py @@ -46,16 +46,19 @@ class OpenAPIImportManager: 资源配置导入manager """ - def __init__(self, gateway: Gateway, data: Dict[str, Any]): + def __init__(self, gateway: Gateway, data: Dict[str, Any], need_delete_unspecified_resources=False): self.version = None self.data = data self.gateway = gateway self._raw_resource_list: List[Dict[str, Any]] = [] self._resource_list: List[ResourceData] = [] self.parser = None + self.need_delete_unspecified_resources = need_delete_unspecified_resources @classmethod - def load_from_content(cls, gateway: Gateway, content: str) -> "OpenAPIImportManager": + def load_from_content( + cls, gateway: Gateway, content: str, need_delete_unspecified_resources=False + ) -> "OpenAPIImportManager": content_format = cls.guess_content_format(content) # 显式地为 loads_func 提供一个类型注解 loads_func: Callable[[str], Dict[str, Any]] = yaml_loads @@ -66,6 +69,7 @@ def load_from_content(cls, gateway: Gateway, content: str) -> "OpenAPIImportMana return cls( gateway=gateway, data=loads_func(content), + need_delete_unspecified_resources=need_delete_unspecified_resources, ) @classmethod @@ -111,7 +115,7 @@ def validate(self) -> List[SchemaValidateErr]: validator = ResourceImportValidator( gateway=self.gateway, resource_data_list=self._resource_list, - need_delete_unspecified_resources=False, + need_delete_unspecified_resources=self.need_delete_unspecified_resources, ) return validator.validate() diff --git a/src/dashboard/apigateway/apigateway/tests/apis/open/resource/test_views.py b/src/dashboard/apigateway/apigateway/tests/apis/open/resource/test_views.py index e7130ffb6..dea37f161 100644 --- a/src/dashboard/apigateway/apigateway/tests/apis/open/resource/test_views.py +++ b/src/dashboard/apigateway/apigateway/tests/apis/open/resource/test_views.py @@ -25,6 +25,8 @@ def test_sync( fake_gateway, fake_default_backend, fake_resource_swagger, + fake_resource, + fake_resource_echo, ignore_related_app_permission, ): resp = request_view( @@ -42,6 +44,8 @@ def test_sync( assert resp.status_code == 200 assert result["code"] == 0 assert len(result["data"]["added"]) == 1 + assert len(result["data"]["deleted"]) == 1 + assert len(result["data"]["updated"]) == 1 def test_sync_with_err_swagger( self, diff --git a/src/dashboard/apigateway/apigateway/tests/conftest.py b/src/dashboard/apigateway/apigateway/tests/conftest.py index adc07df1a..a67b61ed1 100644 --- a/src/dashboard/apigateway/apigateway/tests/conftest.py +++ b/src/dashboard/apigateway/apigateway/tests/conftest.py @@ -245,6 +245,43 @@ def fake_resource(faker, fake_gateway, fake_backend): return resource +@pytest.fixture +def fake_resource_echo(faker, fake_gateway, fake_backend): + resource = G( + Resource, + gateway=fake_gateway, + name="echo", + method="GET", + path="/echo", + ) + ResourceHandler.save_auth_config( + resource.id, + { + "skip_auth_verification": False, + "auth_verified_required": True, + "app_verified_required": True, + "resource_perm_required": True, + }, + ) + G( + Proxy, + type=ProxyTypeEnum.HTTP.value, + resource=resource, + backend=fake_backend, + _config=json.dumps( + { + "method": faker.http_method(), + "path": faker.uri_path(), + "match_subpath": False, + "timeout": faker.random_int(), + } + ), + schema=SchemaFactory().get_proxy_schema(ProxyTypeEnum.HTTP.value), + ) + + return resource + + @pytest.fixture def fake_resource1(faker, fake_resource): resource = deepcopy(fake_resource) @@ -972,7 +1009,27 @@ def fake_resource_swagger(): }, }, }, - } + }, + "/echo": { + "get": { + "operationId": "echo", + "description": "test", + "tags": ["pet"], + "schemes": ["http"], + "x-bk-apigateway-resource": { + "isPublic": True, + "allowApplyPermission": True, + "matchSubpath": True, + "backend": { + "name": "default", + "path": "/hello/2", + "method": "get", + "matchSubpath": True, + "timeout": 30, + }, + }, + }, + }, }, } )