Skip to content

Commit

Permalink
Merge pull request #74 from TencentBlueKing/develop
Browse files Browse the repository at this point in the history
v1.2.2
  • Loading branch information
wklken authored Aug 17, 2022
2 parents 230161f + 2f2da75 commit 798d51f
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 5 deletions.
83 changes: 81 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,37 @@ Resource API Framework 中有两个核心概念:
- `page.limit(int)`:查询数量
- `page.offset(int)`:查询偏移
- 返回值:返回 `iam.resource.provider.ListResult` 的实例,其中 `results` 应满足 IAM search_instance 响应协议
7. `fetch_instance_list(filter, page, **options)`:处理来自 IAM 的 fetch_instance_list 请求,在审计中心生成静态资源快照时,需要实现此方法
- 参数:
- `filter`:过滤器对象
- `filter.start_time(int)`:资源实例变更时间的开始时间(包含start_time)
- `filter.end_time(int)`:资源实例变更时间的结束时间(包含end_time)
- `page`:分页对象
- `page.limit(int)`:查询数量
- `page.offset(int)`:查询偏移
- 返回值:返回 `iam.resource.provider.ListResult` 的实例,其中 `results` 应满足 IAM fetch_instance_list 响应协议
8. `fetch_resource_type_schema(**options)`:处理来自 IAM 的 fetch_resource_type_schema 请求,在审计中心显示静态资源时,需要实现此方法
- 返回值:返回 `iam.resource.provider.SchemaResult` 的实例,输出结果可以通过 [JSON Schema Validator](https://www.jsonschemavalidator.net/) 校验
- `注意`:为满足`审计中心`需求,字段描述新增`description_en``code` 两个 Key,`description_en`指字段英文描述,`code(可选)`指该字段为`代码`内容,在`审计中心`将按代码格式显示
- DEMO(Response.data)
```
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "ID",
"description_en": "ID",
},
"script": {
"type": "string",
"description": "脚本",
"description_en": "Script",
"code": "shell"
}
}
}
```

除此之外,如果 Provider 中定义了 `pre_{method}` 方法(`method` 可选值(`list_attr`, `list_attr_value`, `list_instance`, `fetch_instance_info`, `list_instance_by_policy`),Dispatcher 会在调用对应的 `{method}` 方法前调用其对应的 `pre` 方法进行预处理,下面的例子检测 `list_instance` 中传入的 page 对象,如果 limit 过大,则拒绝该请求:

Expand All @@ -557,7 +588,7 @@ class TaskResourceProvider(ResourceProvider):
下面是一种资源类型的 Provider 定义示例:

```python
from iam.resource.provider import ResourceProvider, ListResult
from iam.resource.provider import ResourceProvider, ListResult, SchemaResult
from task.models import Tasks


Expand Down Expand Up @@ -614,6 +645,18 @@ class TaskResourceProvider(ResourceProvider):
注意, 有翻页; 需要返回count
"""
return ListResult(results=[], count=0)

def fetch_instance_list(self, filter, page, **options):
"""根据过滤条件搜索实例
注意, 有翻页; 需要返回count
"""
return ListResult(results=[], count=0)

def fetch_resource_type_schema(self, **options):
"""获取资源类型 schema 定义
schema定义
"""
return SchemaResult(properties={})
```

### 3.2 Dispatcher
Expand Down Expand Up @@ -648,7 +691,7 @@ class TaskResourceProvider(ResourceProvider):
from blueapps.account.decorators import login_exempt
from iam import IAM
from iam.contrib.django.dispatcher import DjangoBasicResourceApiDispatcher
from iam.resource.provider import ResourceProvider, ListResult
from iam.resource.provider import ResourceProvider, ListResult, SchemaResult
from iam.contrib.converter.queryset import PathEqDjangoQuerySetConverter
from django.conf.urls import url, include

Expand Down Expand Up @@ -763,6 +806,42 @@ class FlowResourceProvider(ResourceProvider):
# TODO
return ListResult(results=[], count=0)

def fetch_instance_list(self, filter, page, **options):
"""
flow
"""
queryset = TaskTemplate.objects.filter(updated_at__gte=filter.start_time).filter(updated_at__lte=filter.end_time)
results = []
for flow in queryset[page.slice_from : page.slice_to]:
results.append(
{
"id": str(flow.id),
"display_name": flow.name,
"creator": flow.creator,
"created_at": flow.created_at,
"updater": flow.updater,
"updated_at": flow.updated_at,
"data": flow.to_json()
}
)
return ListResult(results=results, count=queryset.count())

def fetch_resource_type_schema(self, **options):
properties = {
"id": {
"type": "string",
"description": "ID",
"description_en": "ID",
},
"script": {
"type": "string",
"description": "脚本",
"description_en": "Script",
"code": "shell"
}
}
return SchemaResult(properties=properties)

dispatcher = DjangoBasicResourceApiDispatcher(iam, "my_system")
dispatcher.register("flow", FlowResourceProvider())

Expand Down
2 changes: 1 addition & 1 deletion iam/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-

__version__ = "1.2.1"
__version__ = "1.2.2"
29 changes: 29 additions & 0 deletions iam/contrib/django/dispatcher/dispatchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,32 @@ def _dispatch_search_instance(self, request, data, request_id):
result = provider.search_instance(filter_obj, page_obj, **options)

return success_response(result.to_dict(), request_id)

def _dispatch_fetch_instance_list(self, request, data, request_id):
options = self._get_options(request)

filter_obj = get_filter_obj(data.get("filter"), ["start_time", "end_time"])
page_obj = get_page_obj(data.get("page"))

provider = self._provider[data["type"]]

pre_process = getattr(provider, "pre_fetch_instance_list", None)
if pre_process and callable(pre_process):
pre_process(filter_obj, page_obj, **options)

result = provider.fetch_instance_list(filter_obj, page_obj, **options)

return success_response(result.to_dict(), request_id)

def _dispatch_fetch_resource_type_schema(self, request, data, request_id):
options = self._get_options(request)

provider = self._provider[data["type"]]

pre_process = getattr(provider, "pre_fetch_resource_type_schema", None)
if pre_process and callable(pre_process):
pre_process(**options)

result = provider.fetch_resource_type_schema(**options)

return success_response(result.to_dict(), request_id)
25 changes: 25 additions & 0 deletions iam/resource/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making
蓝鲸智云-权限中心Python SDK(iam-python-sdk) available.
Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""


class SchemaSpecificType(object):
"""
NOTE: don't want to use Enum
"""

ARRAY = "array"
BOOLEAN = "boolean"
INTEGER = "integer"
NULL = "null"
NUMBER = "number"
OBJECT = "object"
STRING = "string"
30 changes: 30 additions & 0 deletions iam/resource/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import six

from iam.resource.constants import SchemaSpecificType


class ListResult(object):
def __init__(self, results, count):
Expand All @@ -31,6 +33,18 @@ def to_list(self):
return self.results


class SchemaResult(object):
def __init__(self, properties):
"""
:param properties: 资源类型 schema 定义
"""
self.type = SchemaSpecificType.OBJECT
self.properties = properties

def to_dict(self):
return {"type": self.type, "properties": self.properties}


@six.add_metaclass(abc.ABCMeta)
class ResourceProvider(object):
@abc.abstractmethod
Expand Down Expand Up @@ -80,3 +94,19 @@ def search_instance(self, filter, page, **options):
return: ListResult
"""
raise NotImplementedError()

def fetch_instance_list(self, filter, page, **options):
"""
处理来自 iam 的 fetch_instance_list 请求
在审计中心生成静态资源快照时,需要实现此方法
return: ListResult
"""
raise NotImplementedError()

def fetch_resource_type_schema(self, **options):
"""
处理来自 iam 的 fetch_resource_type_schema 请求
在审计中心显示静态资源时,需要实现此方法
return: SchemaResult
"""
raise NotImplementedError()
2 changes: 1 addition & 1 deletion iam/resource/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ def slice_to(self):


def get_page_obj(page_data):
return Page(limit=page_data.get("limit", 0), offset=page_data.get("offset", 0))
return Page(limit=int(page_data.get("limit") or 0), offset=int(page_data.get("offset") or 0))
4 changes: 4 additions & 0 deletions release.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
版本日志
===============

# v1.2.2

- add: fetch_instance_list/fetch_resource_type_schema in ResourceProvider

# v1.2.1

- add: operator `string_contains` #68
Expand Down
67 changes: 66 additions & 1 deletion tests/contrib/django/dispatcher/test_dispatchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from iam.contrib.django.dispatcher import DjangoBasicResourceApiDispatcher, InvalidPageException
from iam.exceptions import AuthInvalidOperation
from iam.resource.provider import ListResult, ResourceProvider
from iam.resource.provider import ListResult, SchemaResult, ResourceProvider


def test_basic_resource_api_dispatcher_register():
Expand Down Expand Up @@ -53,6 +53,12 @@ def list_instance_by_policy(self, filter, page, **options):
def search_instance(self, filter, page, **options):
return ListResult(results=[filter, page], count=100)

def fetch_instance_list(self, filter, page, **options):
return ListResult(results=[filter, page], count=100)

def fetch_resource_type_schema(self, **options):
return SchemaResult(properties={"id": {"type": "string"}})

with pytest.raises(AuthInvalidOperation):
dispatcher.register("type", "provider")

Expand Down Expand Up @@ -257,12 +263,16 @@ def __init__(self):
self.fetch_instance_info_spy = {}
self.list_instance_by_policy_spy = {}
self.search_instance_spy = {}
self.fetch_instance_list_spy = {}
self.fetch_resource_type_schema_spy = {}
self.pre_list_attr = MagicMock()
self.pre_list_attr_value = MagicMock()
self.pre_list_instance = MagicMock()
self.pre_fetch_instance_info = MagicMock()
self.pre_list_instance_by_policy = MagicMock()
self.pre_search_instance = MagicMock()
self.pre_fetch_instance_list = MagicMock()
self.pre_fetch_resource_type_schema = MagicMock()

def list_attr(self, **options):
self.list_attr_spy["options"] = options
Expand Down Expand Up @@ -297,6 +307,16 @@ def search_instance(self, filter, page, **options):
self.search_instance_spy["options"] = options
return ListResult(results=["search_instance_token"], count=100)

def fetch_instance_list(self, filter, page, **options):
self.fetch_instance_list_spy["filter"] = filter
self.fetch_instance_list_spy["page"] = page
self.fetch_instance_list_spy["options"] = options
return ListResult(results=["fetch_instance_list_token"], count=100)

def fetch_resource_type_schema(self, **options):
self.fetch_resource_type_schema_spy["options"] = options
return SchemaResult(properties={"fetch_resource_type_schema_token"})

provider = SpyResourceProvider()
dispatcher.register("spy", provider)

Expand Down Expand Up @@ -450,3 +470,48 @@ def search_instance(self, filter, page, **options):
"filter": {"expression": "expression"},
"page": {"limit": "limit", "offset": "offset"},
}

# test fetch_instance_list
fetch_instance_list_req = MagicMock()
fetch_instance_list_req.body = json.dumps(
{
"method": "fetch_instance_list",
"type": "spy",
"filter": {"start_time": 1654012800, "end_time": 1654099199},
"page": {"limit": "limit", "offset": "offset"},
}
)
fetch_instance_list_req.META = {"HTTP_X_REQUEST_ID": "rid", "HTTP_BLUEKING_LANGUAGE": "en"}

resp = dispatcher._dispatch(fetch_instance_list_req)

provider.pre_fetch_instance_list.assert_called_once_with(
{"start_time": 1654012800, "end_time": 1654099199},
{"limit": "limit", "offset": "offset"},
language="en",
)
assert resp["code"] == 0
assert resp["result"] is True
assert resp["data"] == {"count": 100, "results": ["fetch_instance_list_token"]}
assert resp["X-Request-Id"] == "rid"
assert "message" in resp
assert provider.fetch_instance_list_spy == {
"options": {"language": "en"},
"filter": {"start_time": 1654012800, "end_time": 1654099199},
"page": {"limit": "limit", "offset": "offset"},
}

# test fetch_resource_type_schema
fetch_resource_type_schema_req = MagicMock()
fetch_resource_type_schema_req.body = json.dumps({"method": "fetch_resource_type_schema", "type": "spy"})
fetch_resource_type_schema_req.META = {"HTTP_X_REQUEST_ID": "rid", "HTTP_BLUEKING_LANGUAGE": "en"}

resp = dispatcher._dispatch(fetch_resource_type_schema_req)

provider.pre_fetch_resource_type_schema.assert_called_once_with(language="en")
assert resp["code"] == 0
assert resp["result"] is True
assert resp["data"] == SchemaResult(properties={"fetch_resource_type_schema_token"}).to_dict()
assert resp["X-Request-Id"] == "rid"
assert "message" in resp
assert provider.fetch_resource_type_schema_spy == {"options": {"language": "en"}}

0 comments on commit 798d51f

Please sign in to comment.