bkflow-sdk 0.0.28__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bkflow/__init__.py +11 -0
- bkflow/client/__init__.py +10 -0
- bkflow/client/apis/__init__.py +10 -0
- bkflow/client/apis/bkflow.py +342 -0
- bkflow/client/base.py +104 -0
- bkflow/client/core.py +253 -0
- bkflow/common/__init__.py +10 -0
- bkflow/common/decorators.py +107 -0
- bkflow/common/exceptions.py +46 -0
- bkflow/common/validator.py +96 -0
- bkflow/common/views.py +50 -0
- bkflow/config/__init__.py +10 -0
- bkflow/config/default.py +61 -0
- bkflow/interface/__init__.py +10 -0
- bkflow/interface/apps.py +18 -0
- bkflow/interface/serializers.py +270 -0
- bkflow/interface/signals.py +25 -0
- bkflow/interface/urls.py +35 -0
- bkflow/interface/views.py +748 -0
- bkflow_sdk-0.0.28.dist-info/METADATA +248 -0
- bkflow_sdk-0.0.28.dist-info/RECORD +23 -0
- bkflow_sdk-0.0.28.dist-info/WHEEL +5 -0
- bkflow_sdk-0.0.28.dist-info/licenses/LICENSE +21 -0
bkflow/client/core.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tencent is pleased to support the open source community by making 蓝鲸智云 - PaaS平台 (BlueKing - PaaS System) available.
|
|
3
|
+
Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
|
|
4
|
+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://opensource.org/licenses/MIT
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
8
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
9
|
+
specific language governing permissions and limitations under the License.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
import requests
|
|
16
|
+
from django.conf import settings
|
|
17
|
+
|
|
18
|
+
from bkflow.client.apis.bkflow import CollectionsBKFlow
|
|
19
|
+
from bkflow.common.exceptions import APIException
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"get_client_by_request",
|
|
25
|
+
"get_client_by_user",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
HEADER_BK_AUTHORIZATION = "X-Bkapi-Authorization"
|
|
29
|
+
DEFAULT_STAGE = getattr(settings, "BK_APIGW_STAGE_NAME", "prod")
|
|
30
|
+
AVAILABLE_COLLECTIONS = {
|
|
31
|
+
"bkflow": CollectionsBKFlow,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BaseAPIClient:
|
|
36
|
+
"""Base client class for api"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
app_code=None,
|
|
41
|
+
app_secret=None,
|
|
42
|
+
headers=None,
|
|
43
|
+
common_args=None,
|
|
44
|
+
stage="prod",
|
|
45
|
+
timeout=None,
|
|
46
|
+
bk_apigw_ver="v3",
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
:param str app_code: App code to use
|
|
50
|
+
:param str app_secret: App secret to use
|
|
51
|
+
:param dict headers: headers be sent to api
|
|
52
|
+
:param dict common_args: Args that will apply to every request
|
|
53
|
+
:param str stage: Stage for api gateway
|
|
54
|
+
:param int timeout: timeout for request
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
self.app_code = app_code or settings.APP_CODE
|
|
58
|
+
self.app_secret = app_secret or settings.SECRET_KEY
|
|
59
|
+
self.headers = headers or {}
|
|
60
|
+
self.common_args = common_args or {}
|
|
61
|
+
self.stage = stage
|
|
62
|
+
self.bk_apigw_ver = bk_apigw_ver
|
|
63
|
+
self.timeout = timeout
|
|
64
|
+
self._cached_collections = {}
|
|
65
|
+
self._log_warning()
|
|
66
|
+
self.available_collections = AVAILABLE_COLLECTIONS
|
|
67
|
+
|
|
68
|
+
def _log_warning(self):
|
|
69
|
+
_, _, api_name = __package__.partition(".")
|
|
70
|
+
logger.warning(
|
|
71
|
+
"%s is no longer maintained, recommend to use bkapi.%s as a replacement",
|
|
72
|
+
__package__,
|
|
73
|
+
api_name,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def merge_params_data_with_common_args(self, method, params, data):
|
|
77
|
+
"""Add common args to params every request"""
|
|
78
|
+
common_args = dict(app_code=self.app_code, **self.common_args)
|
|
79
|
+
if method in ["GET", "HEAD"]:
|
|
80
|
+
_params = common_args.copy()
|
|
81
|
+
_params.update(params or {})
|
|
82
|
+
params = _params
|
|
83
|
+
elif method in ["POST", "PUT", "PATCH", "DELETE"]:
|
|
84
|
+
_data = common_args.copy()
|
|
85
|
+
_data.update(data or {})
|
|
86
|
+
data = json.dumps(_data)
|
|
87
|
+
return params, data
|
|
88
|
+
|
|
89
|
+
def __getattr__(self, key):
|
|
90
|
+
if key not in self.available_collections:
|
|
91
|
+
return getattr(super(), key)
|
|
92
|
+
|
|
93
|
+
if key not in self._cached_collections:
|
|
94
|
+
collection = self.available_collections[key]
|
|
95
|
+
self._cached_collections[key] = collection(self)
|
|
96
|
+
return self._cached_collections[key]
|
|
97
|
+
|
|
98
|
+
def request(self, method, url, params=None, data=None, path_params=None, **kwargs):
|
|
99
|
+
# Merge custom headers with instance headers
|
|
100
|
+
headers = self.headers.copy()
|
|
101
|
+
if "headers" in kwargs:
|
|
102
|
+
headers.update(kwargs.pop("headers"))
|
|
103
|
+
return requests.request(method, url, headers=headers, params=params, json=data, timeout=self.timeout, **kwargs)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class RedirectAPIClient(BaseAPIClient):
|
|
107
|
+
"""用于根据View的request配置重定向请求."""
|
|
108
|
+
|
|
109
|
+
def redirect(self, request):
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class RequestAPIClient(BaseAPIClient):
|
|
114
|
+
def request(self, *args, **kwargs):
|
|
115
|
+
# update headers
|
|
116
|
+
headers = kwargs.pop("headers", {})
|
|
117
|
+
headers.update(self.headers)
|
|
118
|
+
method = kwargs.get("method", "GET")
|
|
119
|
+
if method.upper() in ["POST", "PUT", "PATCH", "DELETE"]:
|
|
120
|
+
headers["Content-Type"] = "application/json"
|
|
121
|
+
return self._request(headers=headers, *args, **kwargs)
|
|
122
|
+
|
|
123
|
+
def _request(self, method, url, params=None, data=None, **kwargs):
|
|
124
|
+
"""Send request direct"""
|
|
125
|
+
params, data = self.merge_params_data_with_common_args(method, params, data)
|
|
126
|
+
logger.info("Calling %s %s with params=%s, data=%s", method, url, params, data)
|
|
127
|
+
return requests.request(method, url, params=params, json=data, timeout=self.timeout, **kwargs)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_client_by_request(request, stage=DEFAULT_STAGE, common_args=None, headers=None):
|
|
131
|
+
"""
|
|
132
|
+
根据当前请求返回一个client
|
|
133
|
+
:param request: 一个django request实例
|
|
134
|
+
:param stage: 请求环境,默认为prod
|
|
135
|
+
:param common_args: 公共请求参数
|
|
136
|
+
:param headers: 头部信息
|
|
137
|
+
:returns: 一个初始化好的APIClint对象
|
|
138
|
+
"""
|
|
139
|
+
headers = headers or {}
|
|
140
|
+
|
|
141
|
+
is_authenticated = request.user.is_authenticated
|
|
142
|
+
if callable(is_authenticated):
|
|
143
|
+
is_authenticated = is_authenticated()
|
|
144
|
+
if is_authenticated:
|
|
145
|
+
headers.update(
|
|
146
|
+
{
|
|
147
|
+
HEADER_BK_AUTHORIZATION: json.dumps(
|
|
148
|
+
{
|
|
149
|
+
"bk_ticket": request.COOKIES.get("bk_ticket", ""),
|
|
150
|
+
"bk_app_code": settings.APP_CODE,
|
|
151
|
+
"bk_app_secret": settings.SECRET_KEY,
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
raise APIException("用户未通过验证")
|
|
158
|
+
|
|
159
|
+
return RedirectAPIClient(
|
|
160
|
+
app_code=settings.APP_CODE,
|
|
161
|
+
app_secret=settings.SECRET_KEY,
|
|
162
|
+
headers=headers,
|
|
163
|
+
common_args=common_args,
|
|
164
|
+
stage=stage,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_client_by_user(user, stage=DEFAULT_STAGE, common_args=None, headers=None):
|
|
169
|
+
"""
|
|
170
|
+
根据user实例返回一个client
|
|
171
|
+
:param user: 用户
|
|
172
|
+
:param stage: 请求环境,默认为prod
|
|
173
|
+
:param common_args: 公共请求参数
|
|
174
|
+
:param common_args: 公共请求参数
|
|
175
|
+
:param headers: 头部信息
|
|
176
|
+
:returns: 一个初始化好的APIClint对象
|
|
177
|
+
"""
|
|
178
|
+
headers = headers or {}
|
|
179
|
+
common_args = common_args or {}
|
|
180
|
+
if hasattr(user, "username"):
|
|
181
|
+
user = user.username
|
|
182
|
+
try:
|
|
183
|
+
from bkoauth import get_access_token_by_user
|
|
184
|
+
|
|
185
|
+
access_token = get_access_token_by_user(user)
|
|
186
|
+
headers.update(
|
|
187
|
+
{
|
|
188
|
+
HEADER_BK_AUTHORIZATION: json.dumps({"access_token": access_token.access_token}),
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.warning("get_access_token_by_user error %s, using header authorization", str(e))
|
|
193
|
+
headers.update(
|
|
194
|
+
{
|
|
195
|
+
HEADER_BK_AUTHORIZATION: json.dumps(
|
|
196
|
+
{"bk_username": user, "bk_app_code": settings.APP_CODE, "bk_app_secret": settings.SECRET_KEY}
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return RedirectAPIClient(
|
|
202
|
+
app_code=settings.APP_CODE,
|
|
203
|
+
app_secret=settings.SECRET_KEY,
|
|
204
|
+
headers=headers,
|
|
205
|
+
common_args=common_args,
|
|
206
|
+
stage=stage,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def get_redirect_client_with_auth(request, **kwargs):
|
|
211
|
+
"""
|
|
212
|
+
根据当前请求返回一个带有认证信息的client
|
|
213
|
+
:param request: 一个django request实例
|
|
214
|
+
:returns: 一个初始化好的APIClint对象
|
|
215
|
+
"""
|
|
216
|
+
bk_ticket = request.COOKIES.get("bk_ticket", None)
|
|
217
|
+
bk_token = request.COOKIES.get("bk_token", None)
|
|
218
|
+
if not bk_ticket and not bk_token:
|
|
219
|
+
raise APIException("用户未通过验证")
|
|
220
|
+
headers = {
|
|
221
|
+
HEADER_BK_AUTHORIZATION: json.dumps(
|
|
222
|
+
{
|
|
223
|
+
"bk_app_code": settings.APP_CODE,
|
|
224
|
+
"bk_app_secret": settings.SECRET_KEY,
|
|
225
|
+
"bk_ticket": bk_ticket,
|
|
226
|
+
"bk_token": bk_token,
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return RedirectAPIClient(
|
|
232
|
+
app_code=settings.APP_CODE, app_secret=settings.SECRET_KEY, headers=headers, common_args={}, stage=DEFAULT_STAGE
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def get_redirect_client(request):
|
|
237
|
+
"""
|
|
238
|
+
根据当前请求返回一个client
|
|
239
|
+
:param request: 一个django request实例
|
|
240
|
+
:returns: 一个初始化好的APIClint对象
|
|
241
|
+
"""
|
|
242
|
+
headers = {
|
|
243
|
+
HEADER_BK_AUTHORIZATION: json.dumps(
|
|
244
|
+
{
|
|
245
|
+
"bk_app_code": settings.APP_CODE,
|
|
246
|
+
"bk_app_secret": settings.SECRET_KEY,
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return RedirectAPIClient(
|
|
252
|
+
app_code=settings.APP_CODE, app_secret=settings.SECRET_KEY, headers=headers, common_args={}, stage=DEFAULT_STAGE
|
|
253
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tencent is pleased to support the open source community by making 蓝鲸智云 - PaaS平台 (BlueKing - PaaS System) available.
|
|
3
|
+
Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
|
|
4
|
+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://opensource.org/licenses/MIT
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
8
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
9
|
+
specific language governing permissions and limitations under the License.
|
|
10
|
+
"""
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tencent is pleased to support the open source community by making 蓝鲸智云 - PaaS平台 (BlueKing - PaaS System) available.
|
|
3
|
+
Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
|
|
4
|
+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://opensource.org/licenses/MIT
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
8
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
9
|
+
specific language governing permissions and limitations under the License.
|
|
10
|
+
"""
|
|
11
|
+
from functools import wraps
|
|
12
|
+
|
|
13
|
+
from django.core.handlers.wsgi import WSGIRequest
|
|
14
|
+
from django.utils.translation import gettext_lazy as _
|
|
15
|
+
from rest_framework import serializers
|
|
16
|
+
from rest_framework.request import Request
|
|
17
|
+
|
|
18
|
+
from bkflow.common.exceptions import ValidationError
|
|
19
|
+
from bkflow.common.validator import custom_params_valid
|
|
20
|
+
from bkflow.config.default import REQUEST_TOKEN_HEADER_KEY
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def token_inject(view_func):
|
|
24
|
+
"""
|
|
25
|
+
token注入
|
|
26
|
+
:param view_func: 被装饰的view函数
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def wrapper(*args, **kwargs):
|
|
30
|
+
request = args[1]
|
|
31
|
+
if not hasattr(request, "token"):
|
|
32
|
+
token = request.META.get(REQUEST_TOKEN_HEADER_KEY, "")
|
|
33
|
+
|
|
34
|
+
if not token:
|
|
35
|
+
raise ValidationError(
|
|
36
|
+
_("当前接口需要token,请在请求头中添加${REQUEST_TOKEN_HEADER_KEY}").format(
|
|
37
|
+
REQUEST_TOKEN_HEADER_KEY=REQUEST_TOKEN_HEADER_KEY
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
setattr(request, "token", token)
|
|
42
|
+
|
|
43
|
+
return view_func(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
return wraps(view_func)(wrapper)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def params_valid(serializer: serializers.Serializer, add_params: bool = True):
|
|
49
|
+
"""参数校验装饰器
|
|
50
|
+
|
|
51
|
+
:param serializer: serializer类
|
|
52
|
+
:param add_params: 是否将校验后的参数添加到request.cleaned_params中
|
|
53
|
+
:return: 参数校验装饰器
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def decorator(view_func):
|
|
57
|
+
@wraps(view_func)
|
|
58
|
+
def wrapper(*args, **kwargs):
|
|
59
|
+
# 获得Django的request对象
|
|
60
|
+
_request = kwargs.get("request")
|
|
61
|
+
|
|
62
|
+
if not _request:
|
|
63
|
+
for arg in args:
|
|
64
|
+
if isinstance(arg, (Request, WSGIRequest)):
|
|
65
|
+
_request = arg
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
if not _request:
|
|
69
|
+
raise ValidationError(_("该装饰器只允许用于Django的View函数(包括普通View函数和Class-base的View函数)"))
|
|
70
|
+
|
|
71
|
+
# 校验request中的参数
|
|
72
|
+
params = {}
|
|
73
|
+
if _request.method in ["GET"]:
|
|
74
|
+
if isinstance(_request, Request):
|
|
75
|
+
params = _request.query_params
|
|
76
|
+
else:
|
|
77
|
+
params = _request.GET
|
|
78
|
+
elif _request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest":
|
|
79
|
+
if isinstance(_request, Request):
|
|
80
|
+
params = _request.data
|
|
81
|
+
else:
|
|
82
|
+
params = _request.json()
|
|
83
|
+
else:
|
|
84
|
+
if isinstance(_request, Request):
|
|
85
|
+
params = _request.data
|
|
86
|
+
else:
|
|
87
|
+
params = _request.POST
|
|
88
|
+
|
|
89
|
+
cleaned_params = custom_params_valid(serializer=serializer, params=params)
|
|
90
|
+
_request.cleaned_params = cleaned_params
|
|
91
|
+
|
|
92
|
+
# 执行实际的View逻辑
|
|
93
|
+
params_add = False
|
|
94
|
+
try:
|
|
95
|
+
# 语法糖,使用这个decorator的话可直接从view中获得参数的字典
|
|
96
|
+
if "params" not in kwargs and add_params:
|
|
97
|
+
kwargs["params"] = cleaned_params
|
|
98
|
+
params_add = True
|
|
99
|
+
except TypeError:
|
|
100
|
+
if params_add:
|
|
101
|
+
del kwargs["params"]
|
|
102
|
+
resp = view_func(*args, **kwargs)
|
|
103
|
+
return resp
|
|
104
|
+
|
|
105
|
+
return wrapper
|
|
106
|
+
|
|
107
|
+
return decorator
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tencent is pleased to support the open source community by making 蓝鲸智云 - PaaS平台 (BlueKing - PaaS System) available.
|
|
3
|
+
Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
|
|
4
|
+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://opensource.org/licenses/MIT
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
8
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
9
|
+
specific language governing permissions and limitations under the License.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BkflowSDKException(Exception):
|
|
14
|
+
CODE = None
|
|
15
|
+
MESSAGE = None
|
|
16
|
+
STATUS_CODE = 500
|
|
17
|
+
|
|
18
|
+
def __init__(self, message="", errors=""):
|
|
19
|
+
if message:
|
|
20
|
+
self.MESSAGE = message
|
|
21
|
+
if self.MESSAGE and errors:
|
|
22
|
+
self.message = f"{self.MESSAGE}: {errors}"
|
|
23
|
+
elif self.MESSAGE:
|
|
24
|
+
self.message = self.MESSAGE
|
|
25
|
+
else:
|
|
26
|
+
self.message = f"{errors}"
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
return self.message
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class APIException(BkflowSDKException):
|
|
33
|
+
"""Exception for API"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, errors, resp=None, url=""):
|
|
36
|
+
self.url = url
|
|
37
|
+
self.error_message = errors
|
|
38
|
+
self.resp = resp
|
|
39
|
+
|
|
40
|
+
if self.resp is not None:
|
|
41
|
+
errors = "%s, resp=%s" % (errors, self.resp.text)
|
|
42
|
+
super(APIException, self).__init__(errors)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ValidationError(BkflowSDKException):
|
|
46
|
+
pass
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tencent is pleased to support the open source community by making 蓝鲸智云 - PaaS平台 (BlueKing - PaaS System) available.
|
|
3
|
+
Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
|
|
4
|
+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://opensource.org/licenses/MIT
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
8
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
9
|
+
specific language governing permissions and limitations under the License.
|
|
10
|
+
"""
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
from django.utils.translation import gettext_lazy as _
|
|
14
|
+
from rest_framework import serializers
|
|
15
|
+
|
|
16
|
+
from bkflow.common.exceptions import ValidationError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def format_serializer_errors(errors: dict, fields: dict, params: dict, prefix: str = " "):
|
|
20
|
+
"""格式化序列化器错误信息
|
|
21
|
+
|
|
22
|
+
:param errors: 错误信息
|
|
23
|
+
:param fields: 序列化器字段
|
|
24
|
+
:param params: 参数
|
|
25
|
+
:return: 格式化后的错误信息
|
|
26
|
+
"""
|
|
27
|
+
message = _("参数校验失败:{wrap}").format(wrap="\n") if prefix == " " else "\n"
|
|
28
|
+
for key, field_errors in list(errors.items()):
|
|
29
|
+
sub_message = ""
|
|
30
|
+
label = key
|
|
31
|
+
if key not in fields:
|
|
32
|
+
sub_message = json.dumps(field_errors, ensure_ascii=False)
|
|
33
|
+
else:
|
|
34
|
+
field = fields[key]
|
|
35
|
+
label = field.label or field.field_name
|
|
36
|
+
if (
|
|
37
|
+
hasattr(field, "child")
|
|
38
|
+
and isinstance(field_errors, list)
|
|
39
|
+
and len(field_errors) > 0
|
|
40
|
+
and not isinstance(field_errors[0], str)
|
|
41
|
+
):
|
|
42
|
+
for index, sub_errors in enumerate(field_errors):
|
|
43
|
+
if sub_errors:
|
|
44
|
+
sub_format = format_serializer_errors(
|
|
45
|
+
sub_errors, field.child.fields, params, prefix=prefix + " "
|
|
46
|
+
)
|
|
47
|
+
# return sub_format
|
|
48
|
+
sub_message += _("{wrap}{prefix}第{index}项:").format(
|
|
49
|
+
wrap="\n",
|
|
50
|
+
prefix=prefix + " ",
|
|
51
|
+
index=index + 1,
|
|
52
|
+
)
|
|
53
|
+
sub_message += sub_format
|
|
54
|
+
else:
|
|
55
|
+
if isinstance(field_errors, dict):
|
|
56
|
+
if hasattr(field, "child"):
|
|
57
|
+
sub_foramt = format_serializer_errors(
|
|
58
|
+
field_errors, field.child.fields, params, prefix=prefix + " "
|
|
59
|
+
)
|
|
60
|
+
else:
|
|
61
|
+
sub_foramt = format_serializer_errors(field_errors, field.fields, params, prefix=prefix + " ")
|
|
62
|
+
sub_message += sub_foramt
|
|
63
|
+
elif isinstance(field_errors, list):
|
|
64
|
+
for index in range(len(field_errors)):
|
|
65
|
+
field_errors[index] = field_errors[index].format(**{key: params.get(key, "")})
|
|
66
|
+
sub_message += "{index}.{error}".format(index=index + 1, error=field_errors[index])
|
|
67
|
+
sub_message += "\n"
|
|
68
|
+
# 对使用 Validate() 时 label == "non_field_errors" 的特殊情况做处理
|
|
69
|
+
if label == "non_field_errors":
|
|
70
|
+
message += f"{prefix} {sub_message}"
|
|
71
|
+
else:
|
|
72
|
+
message += f"{prefix}{label}: {sub_message}"
|
|
73
|
+
return message
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def custom_params_valid(serializer: serializers.Serializer, params: dict, many: bool = False):
|
|
77
|
+
"""校验参数
|
|
78
|
+
|
|
79
|
+
:param serializer: 校验器
|
|
80
|
+
:param params: 原始参数
|
|
81
|
+
:param many: 是否为多个参数
|
|
82
|
+
:return: 校验通过的参数
|
|
83
|
+
"""
|
|
84
|
+
_serializer = serializer(data=params, many=many)
|
|
85
|
+
try:
|
|
86
|
+
_serializer.is_valid(raise_exception=True)
|
|
87
|
+
except serializers.ValidationError as e: # pylint: disable=broad-except # noqa
|
|
88
|
+
try:
|
|
89
|
+
message = format_serializer_errors(_serializer.errors, _serializer.fields, params)
|
|
90
|
+
except Exception as e: # pylint: disable=broad-except
|
|
91
|
+
if isinstance(e.message, str):
|
|
92
|
+
message = e.message
|
|
93
|
+
else:
|
|
94
|
+
message = _("参数校验失败,详情请查看返回的errors")
|
|
95
|
+
raise ValidationError(message=message, errors=_serializer.errors)
|
|
96
|
+
return list(_serializer.validated_data) if many else dict(_serializer.validated_data)
|
bkflow/common/views.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tencent is pleased to support the open source community by making 蓝鲸智云 - PaaS平台 (BlueKing - PaaS System) available.
|
|
3
|
+
Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
|
|
4
|
+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://opensource.org/licenses/MIT
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
8
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
9
|
+
specific language governing permissions and limitations under the License.
|
|
10
|
+
"""
|
|
11
|
+
import urllib
|
|
12
|
+
|
|
13
|
+
from django.conf import settings
|
|
14
|
+
from rest_framework import status
|
|
15
|
+
from rest_framework.renderers import JSONRenderer
|
|
16
|
+
from rest_framework.response import Response
|
|
17
|
+
from rest_framework.viewsets import GenericViewSet
|
|
18
|
+
|
|
19
|
+
from bkflow.config.default import BK_TOKEN_EXPIRED_CODE
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SimpleGenericViewSet(GenericViewSet):
|
|
23
|
+
"""
|
|
24
|
+
最基础的视图函数,不支持model, view_set 的 创建,查看,更新,删除方法,只支持用户自定义的action
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
renderer_classes = [JSONRenderer]
|
|
28
|
+
EXEMPT_STATUS_CODES = {status.HTTP_204_NO_CONTENT}
|
|
29
|
+
RESPONSE_WRAPPER = None
|
|
30
|
+
|
|
31
|
+
def default_response_wrapper(self, data):
|
|
32
|
+
return {"result": True, "data": data, "code": "0", "message": ""}
|
|
33
|
+
|
|
34
|
+
def finalize_response(self, request, response, *args, **kwargs):
|
|
35
|
+
|
|
36
|
+
if isinstance(response, Response):
|
|
37
|
+
res_data = {
|
|
38
|
+
"result": response.data.get("result"),
|
|
39
|
+
"code": response.data.get("code"),
|
|
40
|
+
"data": response.data.get("data"),
|
|
41
|
+
"message": response.data.get("message"),
|
|
42
|
+
}
|
|
43
|
+
if response.data.get("count"):
|
|
44
|
+
res_data["count"] = response.data.get("count")
|
|
45
|
+
response.data = res_data
|
|
46
|
+
if str(response.data.get("code")) == BK_TOKEN_EXPIRED_CODE:
|
|
47
|
+
response.status_code = 401
|
|
48
|
+
curl = urllib.parse.quote(request.build_absolute_uri())
|
|
49
|
+
res_data["data"] = {"login_url": f"{getattr(settings,'BKPAAS_LOGIN_URL','')}?curl={curl}"}
|
|
50
|
+
return super(SimpleGenericViewSet, self).finalize_response(request, response, *args, **kwargs)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tencent is pleased to support the open source community by making 蓝鲸智云 - PaaS平台 (BlueKing - PaaS System) available.
|
|
3
|
+
Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
|
|
4
|
+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://opensource.org/licenses/MIT
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
8
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
9
|
+
specific language governing permissions and limitations under the License.
|
|
10
|
+
"""
|
bkflow/config/default.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tencent is pleased to support the open source community by making 蓝鲸智云 - PaaS平台 (BlueKing - PaaS System) available.
|
|
3
|
+
Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
|
|
4
|
+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://opensource.org/licenses/MIT
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
8
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
9
|
+
specific language governing permissions and limitations under the License.
|
|
10
|
+
"""
|
|
11
|
+
from django.conf import settings
|
|
12
|
+
|
|
13
|
+
BKFLOW_SDK_APIGW_HOST = getattr(settings, "BKFLOW_SDK_APIGW_HOST", "")
|
|
14
|
+
DEFAULT_SETTINGS = {
|
|
15
|
+
"BKFLOW_SDK_APIGW_HOST": BKFLOW_SDK_APIGW_HOST,
|
|
16
|
+
"BKFLOW_SDK_DEFAULT_SPACE_ID": "",
|
|
17
|
+
"common_key1": "common_value2",
|
|
18
|
+
"interface": {"interface_key1": "interface_value1"},
|
|
19
|
+
}
|
|
20
|
+
REQUEST_TOKEN_HEADER_KEY = "HTTP_BKFLOW_TOKEN"
|
|
21
|
+
API_TOKEN_HEADER_KEY = "BKFLOW-TOKEN"
|
|
22
|
+
BK_TOKEN_EXPIRED_CODE = "1640001"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BkflowSDKSettings:
|
|
26
|
+
SETTING_PREFIX = "BKFLOW_SDK"
|
|
27
|
+
NESTING_SEPARATOR = "_"
|
|
28
|
+
|
|
29
|
+
def __init__(self, default_settings=None):
|
|
30
|
+
self.project_settings = self.get_flatten_settings(getattr(settings, self.SETTING_PREFIX, {}))
|
|
31
|
+
self.default_settings = self.get_flatten_settings(default_settings or DEFAULT_SETTINGS)
|
|
32
|
+
|
|
33
|
+
def __getattr__(self, key):
|
|
34
|
+
if key not in self.project_settings and key not in self.default_settings:
|
|
35
|
+
raise AttributeError
|
|
36
|
+
|
|
37
|
+
value = self.project_settings.get(key) or self.default_settings.get(key)
|
|
38
|
+
if value is not None:
|
|
39
|
+
setattr(self, key, value)
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
def get_flatten_settings(self, inputted_settings: dict, cur_prefix: str = ""):
|
|
43
|
+
def get_cur_key(cur_key):
|
|
44
|
+
return f"{cur_prefix}{self.NESTING_SEPARATOR}{cur_key}" if cur_prefix else cur_key
|
|
45
|
+
|
|
46
|
+
flatten_settings = {}
|
|
47
|
+
for key, value in inputted_settings.items():
|
|
48
|
+
if isinstance(value, dict):
|
|
49
|
+
flatten_sub_settings = self.get_flatten_settings(value, key)
|
|
50
|
+
flatten_settings.update(
|
|
51
|
+
{
|
|
52
|
+
get_cur_key(flatten_key): flatten_value
|
|
53
|
+
for flatten_key, flatten_value in flatten_sub_settings.items()
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
flatten_settings[get_cur_key(key)] = value
|
|
58
|
+
return flatten_settings
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
bkflow_sdk_settings = BkflowSDKSettings(DEFAULT_SETTINGS)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tencent is pleased to support the open source community by making 蓝鲸智云 - PaaS平台 (BlueKing - PaaS System) available.
|
|
3
|
+
Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
|
|
4
|
+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://opensource.org/licenses/MIT
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
8
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
9
|
+
specific language governing permissions and limitations under the License.
|
|
10
|
+
"""
|
bkflow/interface/apps.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tencent is pleased to support the open source community by making 蓝鲸智云 - PaaS平台 (BlueKing - PaaS System) available.
|
|
3
|
+
Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
|
|
4
|
+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://opensource.org/licenses/MIT
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
8
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
9
|
+
specific language governing permissions and limitations under the License.
|
|
10
|
+
"""
|
|
11
|
+
from django.apps import AppConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BkflowInterfaceConfig(AppConfig):
|
|
15
|
+
name = "bkflow.interface"
|
|
16
|
+
|
|
17
|
+
def ready(self):
|
|
18
|
+
pass
|