Unverified Commit a379e6aa authored by schnee's avatar schnee Committed by GitHub
Browse files

feature: dashboard CustomResource API V2 (#1239)

* feature: dashboard CRD list and retrieve API

* minor: Cobj ApiVersion 解析逻辑抽离,补充单元测试

* feature: CustomObjects CURD API

* feature: subscribe API support CustomObject

* feature: examples api support custom_object.

* test: 补充 CRD,CustomObject 单元测试

* minor: update comments

* feature: web_annotations 补充 CRD 自定义展示列
minor: resolve #1239 conversations

* minor: subscribe API drop param custom_obj_kind

* test: mock custom_resource api permissions
Showing with 372 additions and 37 deletions
+372 -37
......@@ -77,6 +77,9 @@ class ResourceType(str, StructuredEnum):
StorageClass = EnumField('storageclass', 'StorageClass')
# rbac
ServiceAccount = EnumField('serviceaccount', 'ServiceAccount')
# CustomResource
CRD = EnumField('crd', _('自定义资源定义'))
CustomObject = EnumField('customobject', _('自定义对象'))
ResourceTypes = dict(ResourceType.get_choices())
......
......@@ -60,6 +60,7 @@ class CustomObjectViewSet(SystemViewSet):
return Response()
def patch_custom_object_scale(self, request, project_id, cluster_id, crd_name, name):
""" 自定义资源扩缩容 """
req_data = request.data.copy()
req_data["crd_name"] = crd_name
serializer = PatchCustomObjectScaleSLZ(data=req_data)
......
# -*- coding: utf-8 -*-
#
# Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available.
# Copyright (C) 2017-2019 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.
#
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
class OptionalNamespaceSLZ(serializers.Serializer):
# 部分资源对象是可以没有命名空间维度的
namespace = serializers.CharField(label=_('命名空间'), required=False)
class FetchCustomObjectSLZ(OptionalNamespaceSLZ):
""" 获取单个自定义对象 """
class CreateCustomObjectSLZ(serializers.Serializer):
""" 创建自定义对象 """
manifest = serializers.JSONField(label=_('资源配置信息'))
class UpdateCustomObjectSLZ(CreateCustomObjectSLZ):
""" 更新(replace)某个自定义对象 """
class DestroyCustomObjectSLZ(OptionalNamespaceSLZ):
""" 删除单个自定义对象 """
# -*- coding: utf-8 -*-
#
# Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available.
# Copyright (C) 2017-2019 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.
#
from rest_framework import routers
from .views import CRDViewSet, CustomObjectViewSet
router = routers.DefaultRouter(trailing_slash=True)
router.register(r'(?P<crd_name>[\w\.-]+)/custom_objects', CustomObjectViewSet, basename='custom_object')
router.register(r'', CRDViewSet, basename='crd')
# -*- coding: utf-8 -*-
#
# Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available.
# Copyright (C) 2017-2019 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.
#
from typing import Dict
from django.utils.translation import ugettext_lazy as _
from backend.dashboard.exceptions import ResourceNotExist
from backend.dashboard.permissions import gen_web_annotations
from backend.resources.custom_object import CustomResourceDefinition
def gen_cobj_web_annotations(request, project_id, cluster_id, crd_name: str) -> Dict:
""" 构造 custom_object 相关 web_annotations """
client = CustomResourceDefinition(request.ctx_cluster)
crd = client.get(name=crd_name, is_format=False)
if not crd:
raise ResourceNotExist(_('集群 {} 中未注册自定义资源 {}').format(cluster_id, crd_name))
# 先获取基础的,仅包含权限信息的 web_annotations
web_annotations = gen_web_annotations(request, project_id, cluster_id)
web_annotations.update({'additional_columns': crd.additional_columns})
return web_annotations
# -*- coding: utf-8 -*-
#
# Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available.
# Copyright (C) 2017-2019 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.
#
from django.utils.translation import ugettext_lazy as _
from kubernetes.dynamic.exceptions import DynamicApiError
from rest_framework.response import Response
from backend.bcs_web.audit_log.audit.decorators import log_audit_on_view
from backend.bcs_web.audit_log.constants import ActivityType
from backend.bcs_web.viewsets import SystemViewSet
from backend.dashboard.auditor import DashboardAuditor
from backend.dashboard.custom_object_v2 import serializers as slzs
from backend.dashboard.custom_object_v2.utils import gen_cobj_web_annotations
from backend.dashboard.exceptions import CreateResourceError, DeleteResourceError, UpdateResourceError
from backend.dashboard.permissions import validate_cluster_perm
from backend.dashboard.utils.resp import ListApiRespBuilder, RetrieveApiRespBuilder
from backend.resources.constants import KUBE_NAME_REGEX, K8sResourceKind
from backend.resources.custom_object import CustomResourceDefinition, get_cobj_client_by_crd
from backend.resources.custom_object.formatter import CustomObjectCommonFormatter
from backend.utils.basic import getitems
from backend.utils.response import BKAPIResponse
class CRDViewSet(SystemViewSet):
""" 自定义资源定义 """
lookup_field = 'crd_name'
# 指定符合 CRD 名称规范的
lookup_value_regex = KUBE_NAME_REGEX
def list(self, request, project_id, cluster_id):
""" 获取所有自定义资源列表 """
client = CustomResourceDefinition(request.ctx_cluster)
response_data = ListApiRespBuilder(client).build()
return Response(response_data)
def retrieve(self, request, project_id, cluster_id, crd_name):
""" 获取单个自定义资源详情 """
client = CustomResourceDefinition(request.ctx_cluster)
response_data = RetrieveApiRespBuilder(client, namespace=None, name=crd_name).build()
return Response(response_data)
class CustomObjectViewSet(SystemViewSet):
""" 自定义资源对象 """
lookup_field = 'custom_obj_name'
lookup_value_regex = KUBE_NAME_REGEX
def list(self, request, project_id, cluster_id, crd_name):
""" 获取某类自定义资源列表 """
client = get_cobj_client_by_crd(request.ctx_cluster, crd_name)
response_data = ListApiRespBuilder(client, formatter=CustomObjectCommonFormatter()).build()
web_annotations = gen_cobj_web_annotations(request, project_id, cluster_id, crd_name)
return BKAPIResponse(response_data, web_annotations=web_annotations)
def retrieve(self, request, project_id, cluster_id, crd_name, custom_obj_name):
""" 获取单个自定义对象 """
params = self.params_validate(slzs.FetchCustomObjectSLZ)
client = get_cobj_client_by_crd(request.ctx_cluster, crd_name)
response_data = RetrieveApiRespBuilder(
client, namespace=params.get('namespace'), name=custom_obj_name, formatter=CustomObjectCommonFormatter()
).build()
return Response(response_data)
@log_audit_on_view(DashboardAuditor, activity_type=ActivityType.Add)
def create(self, request, project_id, cluster_id, crd_name):
""" 创建自定义资源 """
validate_cluster_perm(request, project_id, cluster_id)
params = self.params_validate(slzs.CreateCustomObjectSLZ)
namespace = getitems(params, 'manifest.metadata.namespace')
cus_obj_name = getitems(params, 'manifest.metadata.name')
self._update_audit_ctx(request, namespace, crd_name, cus_obj_name)
client = get_cobj_client_by_crd(request.ctx_cluster, crd_name)
try:
response_data = client.create(namespace=namespace, body=params['manifest'], is_format=False).data.to_dict()
except DynamicApiError as e:
raise CreateResourceError(_('创建资源失败: {}').format(e.summary()))
except ValueError as e:
raise CreateResourceError(_('创建资源失败: {}').format(str(e)))
return Response(response_data)
@log_audit_on_view(DashboardAuditor, activity_type=ActivityType.Modify)
def update(self, request, project_id, cluster_id, crd_name, custom_obj_name):
""" 更新自定义资源 """
validate_cluster_perm(request, project_id, cluster_id)
params = self.params_validate(slzs.UpdateCustomObjectSLZ)
namespace = getitems(params, 'manifest.metadata.namespace')
self._update_audit_ctx(request, namespace, crd_name, custom_obj_name)
client = get_cobj_client_by_crd(request.ctx_cluster, crd_name)
manifest = params['manifest']
# 自定义资源 Replace 也需要指定 resourceVersion
# 这里先 pop,通过在 replace 指定 auto_add_version=True 添加
manifest['metadata'].pop('resourceVersion', None)
try:
response_data = client.replace(
body=manifest, namespace=namespace, name=custom_obj_name, is_format=False, auto_add_version=True
).data.to_dict()
except DynamicApiError as e:
raise UpdateResourceError(_('更新资源失败: {}').format(e.summary()))
except ValueError as e:
raise UpdateResourceError(_('更新资源失败: {}').format(str(e)))
return Response(response_data)
@log_audit_on_view(DashboardAuditor, activity_type=ActivityType.Delete)
def destroy(self, request, project_id, cluster_id, crd_name, custom_obj_name):
""" 删除自定义资源 """
validate_cluster_perm(request, project_id, cluster_id)
params = self.params_validate(slzs.DestroyCustomObjectSLZ)
namespace = params.get('namespace') or None
self._update_audit_ctx(request, namespace, crd_name, custom_obj_name)
client = get_cobj_client_by_crd(request.ctx_cluster, crd_name)
try:
response_data = client.delete(name=custom_obj_name, namespace=namespace).to_dict()
except DynamicApiError as e:
raise DeleteResourceError(_('删除资源失败: {}').format(e.summary()))
return Response(response_data)
@staticmethod
def _update_audit_ctx(request, namespace: str, crd_name: str, custom_obj_name: str) -> None:
""" 更新操作审计相关信息 """
resource_name = (
f'{crd_name} - {namespace}/{custom_obj_name}' if namespace else f'{crd_name} - {custom_obj_name}'
)
request.audit_ctx.update_fields(
resource_type=K8sResourceKind.CustomObject.value.lower(), resource=resource_name
)
......@@ -52,4 +52,6 @@ RES_KIND_WITH_DEMO_MANIFEST = [
K8sResourceKind.HorizontalPodAutoscaler.value,
# rbac
K8sResourceKind.ServiceAccount.value,
# CustomResource
K8sResourceKind.CustomObject.value,
]
......@@ -13,6 +13,7 @@
#
from backend.resources.configs.configmap import ConfigMap
from backend.resources.configs.secret import Secret
from backend.resources.custom_object import CustomResourceDefinition
from backend.resources.event.client import Event
from backend.resources.hpa.client import HPA
from backend.resources.namespace.client import Namespace
......@@ -59,6 +60,8 @@ K8S_RESOURCE_CLIENTS = [
# configurations
ConfigMap,
Secret,
# CustomResource
CustomResourceDefinition,
# cluster
Event,
Namespace,
......
......@@ -11,14 +11,23 @@
# 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.
#
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from backend.resources.constants import K8sResourceKind
from .constants import KIND_RESOURCE_CLIENT_MAP
class FetchResourceWatchResultSLZ(serializers.Serializer):
"""获取单类资源一段时间内的变更记录"""
resource_version = serializers.CharField(label='资源版本号', max_length=32)
kind = serializers.ChoiceField(label='资源类型', choices=K8sResourceKind.get_choices())
resource_version = serializers.CharField(label=_('资源版本号'), max_length=32)
kind = serializers.CharField(label=_('资源类型'), max_length=128)
api_version = serializers.CharField(label=_('API版本'), max_length=16, required=False)
def validate(self, attrs):
""" 若不是确定支持的资源类型(如自定义资源),则需要提供 apiVersion,以免需先查询 CRD """
if attrs['kind'] not in KIND_RESOURCE_CLIENT_MAP:
if not attrs.get('api_version'):
raise ValidationError(_('当资源类型为自定义对象时,需要指定 ApiVersion'))
return attrs
......@@ -23,6 +23,8 @@ from backend.dashboard.subscribe.constants import (
KIND_RESOURCE_CLIENT_MAP,
)
from backend.dashboard.subscribe.serializers import FetchResourceWatchResultSLZ
from backend.resources.constants import K8sResourceKind
from backend.resources.custom_object import CustomObject
from backend.utils.basic import getitems
......@@ -33,9 +35,13 @@ class SubscribeViewSet(SystemViewSet):
"""获取指定资源某resource_version后变更记录"""
params = self.params_validate(FetchResourceWatchResultSLZ)
# 根据 Kind 获取对应的 K8S Resource Client 并初始化
Client = KIND_RESOURCE_CLIENT_MAP[params['kind']]
resource_client = Client(request.ctx_cluster)
if params['kind'] in KIND_RESOURCE_CLIENT_MAP:
# 根据 Kind 获取对应的 K8S Resource Client 并初始化
Client = KIND_RESOURCE_CLIENT_MAP[params['kind']]
resource_client = Client(request.ctx_cluster)
else:
# 自定义资源类型走特殊的获取 ResourceClient 逻辑
resource_client = CustomObject(request.ctx_cluster, kind=params['kind'], api_version=params['api_version'])
res_version = params['resource_version']
try:
events = resource_client.watch(resource_version=res_version, timeout=DEFAULT_SUBSCRIBE_TIMEOUT)
......
......@@ -14,6 +14,7 @@
from django.conf.urls import include, url
from backend.dashboard.configs.urls import router as config_router
from backend.dashboard.custom_object_v2.urls import router as custom_obj_router
from backend.dashboard.events.urls import router as event_router
from backend.dashboard.examples.urls import router as example_router
from backend.dashboard.hpa.urls import router as hpa_router
......@@ -35,6 +36,8 @@ namespace_prefix_urlpatterns = [
]
urlpatterns = [
# TODO 自定义资源暂时保持 V1,V2 两个版本,后续会逐步替换掉 V1
url(r"^crds/v2/", include(custom_obj_router.urls)),
url(r"^crds/", include("backend.dashboard.custom_object.urls")),
url(r"^events/", include(event_router.urls)),
url(r"^namespaces/", include(namespace_router.urls)),
......
......@@ -11,24 +11,27 @@
# 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.
#
from typing import Dict
from typing import Dict, Optional, Union
from django.utils.translation import ugettext_lazy as _
from backend.dashboard.exceptions import ResourceNotExist
from backend.resources.resource import ResourceClient, ResourceList
from backend.resources.utils.format import ResourceFormatter
class ListApiRespBuilder:
""" 构造 Dashboard 资源列表 Api 响应内容逻辑 """
def __init__(self, client: ResourceClient, **kwargs):
def __init__(self, client: ResourceClient, formatter: Optional[ResourceFormatter] = None, **kwargs):
"""
构造器初始化
:param client: 资源客户端
:param formatter: 资源格式化器(默认使用 client.formatter)
"""
self.client = client
self.formatter = formatter if formatter else self.client.formatter
self.resources = self.client.list(is_format=False, **kwargs)
# 兼容处理,若为 ResourceList 需要将其 data (ResourceInstance) 转换成 dict
if isinstance(self.resources, ResourceList):
......@@ -39,7 +42,7 @@ class ListApiRespBuilder:
result = {
'manifest': self.resources,
'manifest_ext': {
item['metadata']['uid']: self.client.formatter.format_dict(item) for item in self.resources['items']
item['metadata']['uid']: self.formatter.format_dict(item) for item in self.resources['items']
},
}
return result
......@@ -48,15 +51,24 @@ class ListApiRespBuilder:
class RetrieveApiRespBuilder:
""" 构造 Dashboard 资源详情 Api 响应内容逻辑 """
def __init__(self, client: ResourceClient, namespace: str, name: str, **kwargs):
def __init__(
self,
client: ResourceClient,
namespace: Union[str, None],
name: str,
formatter: Optional[ResourceFormatter] = None,
**kwargs
):
"""
构造器初始化
:param client: 资源客户端
:param namespace: 资源命名空间
:param name: 资源名称
:param formatter: 资源格式化器(默认使用 client.formatter)
"""
self.client = client
self.formatter = formatter if formatter else self.client.formatter
raw_resource = self.client.get(namespace=namespace, name=name, is_format=False, **kwargs)
if not raw_resource:
raise ResourceNotExist(_('资源 {}/{} 不存在').format(namespace, name))
......@@ -66,5 +78,5 @@ class RetrieveApiRespBuilder:
""" 组装 Dashboard Api 响应内容 """
return {
'manifest': self.resource,
'manifest_ext': self.client.formatter.format_dict(self.resource),
'manifest_ext': self.formatter.format_dict(self.resource),
}
......@@ -23,6 +23,7 @@ from backend.dashboard.exceptions import CreateResourceError, DeleteResourceErro
from backend.dashboard.permissions import gen_web_annotations, validate_cluster_perm
from backend.dashboard.serializers import CreateResourceSLZ, ListResourceSLZ, UpdateResourceSLZ
from backend.dashboard.utils.resp import ListApiRespBuilder, RetrieveApiRespBuilder
from backend.resources.constants import KUBE_NAME_REGEX
from backend.utils.basic import getitems
from backend.utils.response import BKAPIResponse
......@@ -121,3 +122,4 @@ class DashboardViewSet(ListAndRetrieveMixin, DestroyMixin, CreateMixin, UpdateMi
"""
lookup_field = 'name'
lookup_value_regex = KUBE_NAME_REGEX
......@@ -69,6 +69,9 @@ class K8sResourceKind(ChoicesEnum):
StorageClass = "StorageClass"
# rbac
ServiceAccount = "ServiceAccount"
# CustomResource
CustomResourceDefinition = "CustomResourceDefinition"
CustomObject = "CustomObject"
# hpa
HorizontalPodAutoscaler = "HorizontalPodAutoscaler"
# other
......@@ -98,6 +101,9 @@ class K8sResourceKind(ChoicesEnum):
(StorageClass, "StorageClass"),
# rbac
(ServiceAccount, "ServiceAccount"),
# CustomResource
(CustomResourceDefinition, "CustomResourceDefinition"),
(CustomObject, "CustomObject"),
# hpa
(HorizontalPodAutoscaler, "HorizontalPodAutoscaler"),
# other
......
......@@ -11,17 +11,30 @@
# 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.
#
from typing import Optional
from typing import List, Optional, Type
from backend.container_service.clusters.base.models import CtxCluster
from backend.resources.constants import K8sResourceKind
from backend.utils.basic import getitems
from ..resource import ResourceClient
from ..resource import ResourceClient, ResourceObj
from .constants import PREFERRED_CRD_API_VERSION
from .format import CRDFormatter
from .formatter import CRDFormatter
class CrdObj(ResourceObj):
@property
def additional_columns(self) -> List:
""" 获取 资源新增列 信息 """
manifest = self.data.to_dict()
additional_columns = getitems(manifest, 'spec.additionalPrinterColumns', [])
# 存在时间会统一处理,因此此处直接过滤掉
return [col for col in additional_columns if col['name'].lower() != 'age']
class CustomResourceDefinition(ResourceClient):
kind = "CustomResourceDefinition"
kind = K8sResourceKind.CustomResourceDefinition.value
result_type: Type['ResourceObj'] = CrdObj
formatter = CRDFormatter()
def __init__(self, ctx_cluster: CtxCluster, api_version: Optional[str] = PREFERRED_CRD_API_VERSION):
......
......@@ -18,9 +18,10 @@ from django.utils.translation import ugettext_lazy as _
from backend.container_service.clusters.base.models import CtxCluster
from backend.utils.error_codes import error_codes
from ..resource import ResourceClient, ResourceObj
from ..resource import ResourceClient
from .crd import CustomResourceDefinition
from .format import CustomObjectFormatter
from .formatter import CustomObjectFormatter
from .utils import parse_cobj_api_version
class CustomObject(ResourceClient):
......@@ -31,26 +32,11 @@ class CustomObject(ResourceClient):
super().__init__(ctx_cluster, api_version)
def _get_cobj_api_version(crd: ResourceObj) -> str:
# https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#specify-multiple-versions
group = crd.data.spec.group
versions = crd.data.spec.versions
if versions:
for v in versions:
if v.served:
return f"{group}/{v.name}"
return f"{group}/{versions[0].name}"
if crd.data.spec.version:
return f"{group}/{crd.data.spec.version}"
return f"{group}/v1alpha1"
def get_cobj_client_by_crd(ctx_cluster: CtxCluster, crd_name: str) -> CustomObject:
crd_client = CustomResourceDefinition(ctx_cluster)
crd = crd_client.get(name=crd_name, is_format=False)
if crd:
return CustomObject(ctx_cluster, kind=crd.data.spec.names.kind, api_version=_get_cobj_api_version(crd))
return CustomObject(
ctx_cluster, kind=crd.data.spec.names.kind, api_version=parse_cobj_api_version(crd.data.to_dict())
)
raise error_codes.ResNotFoundError(_("集群({})中未注册自定义资源({})").format(ctx_cluster.id, crd_name))
......@@ -13,15 +13,28 @@
#
from typing import Dict
from backend.resources.custom_object.utils import parse_cobj_api_version
from backend.resources.utils.format import ResourceDefaultFormatter
from backend.utils.basic import getitems
class CRDFormatter(ResourceDefaultFormatter):
def format_dict(self, resource_dict: Dict) -> Dict:
return {"name": getitems(resource_dict, "metadata.name"), "scope": getitems(resource_dict, "spec.scope")}
return {
'name': getitems(resource_dict, 'metadata.name'),
'scope': getitems(resource_dict, 'spec.scope'),
'kind': getitems(resource_dict, 'spec.names.kind'),
'api_version': parse_cobj_api_version(resource_dict),
}
class CustomObjectFormatter(ResourceDefaultFormatter):
def format_dict(self, resource_dict: Dict) -> Dict:
return resource_dict
class CustomObjectCommonFormatter(ResourceDefaultFormatter):
""" 通用的自定义对象格式化器 """
def format_dict(self, resource_dict: Dict) -> Dict:
return self.format_common_dict(resource_dict)
# -*- coding: utf-8 -*-
#
# Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available.
# Copyright (C) 2017-2019 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.
#
from typing import Dict
from backend.utils.basic import getitems
def parse_cobj_api_version(crd: Dict) -> str:
"""
根据 CRD 配置解析 cobj api_version
ref: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#specify-multiple-versions # noqa
"""
group = getitems(crd, 'spec.group')
versions = getitems(crd, 'spec.versions')
if versions:
for v in versions:
if v['served']:
return f"{group}/{v['name']}"
return f"{group}/{versions[0]['name']}"
version = getitems(crd, 'spec.version')
if version:
return f"{group}/{version}"
return f"{group}/v1alpha1"
{
"kind": "CustomObject",
"class": null,
"references": null,
"items": []
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment