Skip to content

Commit

Permalink
feature: 提供 Agent 包管理后台接口 (closed #1683)
Browse files Browse the repository at this point in the history
  • Loading branch information
wyyalt committed Jan 26, 2024
1 parent 4526734 commit 4f30709
Show file tree
Hide file tree
Showing 13 changed files with 932 additions and 0 deletions.
4 changes: 4 additions & 0 deletions apps/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ def get_serializer_class(self, *args, **kwargs):
return type(self.serializer_class.__name__, (self.serializer_class,), {"Meta": self.serializer_meta})


class ApiMixinModelViewSet(ApiMixin, _ModelViewSet):
pagination_class = DataPageNumberPagination


def custom_exception_handler(exc, context):
"""
自定义错误处理方式
Expand Down
28 changes: 28 additions & 0 deletions apps/node_man/handlers/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,32 @@ def fetch_os_type_children(os_types: Tuple = constants.OsType):
os_type_children.append({"id": os_type, "name": constants.OS_CHN.get(os_type, os_type)})
return os_type_children

@staticmethod
def fetch_agent_pkg_manager_children():
mock_version = [
{"name": "2.1.8", "id": "2.1.8"},
{"name": "2.1.7", "id": "2.1.7"},
]
mock_tags = [
{"name": "稳定版本", "id": "stable"},
{"name": "最新版本", "id": "latest"},
]
mock_creator = [
{"name": "user1", "id": "user1"},
{"name": "user2", "id": "user2"},
]
mock_is_ready = [
{"name": "启用", "id": True},
{"name": "停用", "id": False},
]

return [
{"name": _("版本号"), "id": "version", "children": mock_version},
{"name": _("标签信息"), "id": "tags", "children": mock_tags},
{"name": _("上传用户"), "id": "creator", "children": mock_creator},
{"name": _("状态"), "id": "is_ready", "children": mock_is_ready},
]

def filter_condition(self, category, params=None):
"""
获取过滤条件
Expand Down Expand Up @@ -538,6 +564,8 @@ def filter_condition(self, category, params=None):
elif category == "os_type":
ret = self.fetch_os_type_children()
return ret
elif category == "agent_pkg_manage":
return self.fetch_agent_pkg_manager_children()

@staticmethod
def install_default_values_formatter(install_default_values: Dict[str, Dict[str, Any]]):
Expand Down
97 changes: 97 additions & 0 deletions apps/node_man/migrations/0081_gsepackagedesc_gsepackages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available.
Copyright (C) 2017-2022 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 https://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.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("node_man", "0080_auto_20231122_1552"),
]

operations = [
migrations.CreateModel(
name="GsePackageDesc",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_time", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")),
("created_by", models.CharField(default="", max_length=32, verbose_name="创建者")),
("updated_time", models.DateTimeField(auto_now=True, null=True, verbose_name="更新时间")),
("updated_by", models.CharField(blank=True, default="", max_length=32, verbose_name="修改者")),
("project", models.CharField(db_index=True, max_length=32, unique=True, verbose_name="工程名")),
("description", models.TextField(verbose_name="安装包描述")),
("description_en", models.TextField(blank=True, null=True, verbose_name="英文插件描述")),
(
"category",
models.CharField(
choices=[("official", "official"), ("external", "external"), ("scripts", "scripts")],
max_length=32,
verbose_name="所属范围",
),
),
],
options={
"verbose_name": "Gse包描述(GsePackageDesc)",
"verbose_name_plural": "Gse包描述(GsePackageDesc)",
},
),
migrations.CreateModel(
name="GsePackages",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_time", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")),
("created_by", models.CharField(default="", max_length=32, verbose_name="创建者")),
("updated_time", models.DateTimeField(auto_now=True, null=True, verbose_name="更新时间")),
("updated_by", models.CharField(blank=True, default="", max_length=32, verbose_name="修改者")),
("pkg_name", models.CharField(max_length=128, verbose_name="压缩包名")),
("version", models.CharField(max_length=128, verbose_name="版本号")),
("project", models.CharField(db_index=True, max_length=32, verbose_name="工程名")),
("pkg_size", models.IntegerField(verbose_name="包大小")),
("pkg_path", models.CharField(max_length=128, verbose_name="包路径")),
("md5", models.CharField(max_length=32, verbose_name="md5值")),
("location", models.CharField(max_length=512, verbose_name="安装包链接")),
(
"os",
models.CharField(
choices=[("windows", "windows"), ("linux", "linux"), ("aix", "aix"), ("solaris", "solaris")],
db_index=True,
default="linux",
max_length=32,
verbose_name="系统类型",
),
),
(
"cpu_arch",
models.CharField(
choices=[
("x86", "x86"),
("x86_64", "x86_64"),
("powerpc", "powerpc"),
("aarch64", "aarch64"),
("sparc", "sparc"),
],
db_index=True,
default="x86_64",
max_length=32,
verbose_name="CPU类型",
),
),
("is_ready", models.BooleanField(default=True, verbose_name="插件是否可用")),
("version_log", models.TextField(blank=True, null=True, verbose_name="版本日志")),
("version_log_en", models.TextField(blank=True, null=True, verbose_name="英文版本日志")),
],
options={
"verbose_name": "Gse包(GsePackages)",
"verbose_name_plural": "Gse包(GsePackages)",
},
),
]
46 changes: 46 additions & 0 deletions apps/node_man/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2501,3 +2501,49 @@ class Meta:
index_together = [
["bk_biz_id", "enable"],
]


class GsePackages(orm.OperateRecordModel):
pkg_name = models.CharField(_("压缩包名"), max_length=128)
version = models.CharField(_("版本号"), max_length=128)
project = models.CharField(_("工程名"), max_length=32, db_index=True)
pkg_size = models.IntegerField(_("包大小"))
pkg_path = models.CharField(_("包路径"), max_length=128)
md5 = models.CharField(_("md5值"), max_length=32)
location = models.CharField(_("安装包链接"), max_length=512)
os = models.CharField(
_("系统类型"),
max_length=32,
choices=constants.PLUGIN_OS_CHOICES,
default=constants.PluginOsType.linux,
db_index=True,
)
cpu_arch = models.CharField(
_("CPU类型"), max_length=32, choices=constants.CPU_CHOICES, default=constants.CpuType.x86_64, db_index=True
)

# 由于创建记录时,文件可能仍然在传输过程中,因此需要标志位判断是否已经可用
is_ready = models.BooleanField(_("插件是否可用"), default=True)

version_log = models.TextField(_("版本日志"), null=True, blank=True)
version_log_en = models.TextField(_("英文版本日志"), null=True, blank=True)

class Meta:
verbose_name = _("Gse包(GsePackages)")
verbose_name_plural = _("Gse包(GsePackages)")


class GsePackageDesc(orm.OperateRecordModel):
"""
Gse包描述表
"""

# 安装包名需要全局唯一,防止冲突
project = models.CharField(_("工程名"), max_length=32, unique=True, db_index=True)
description = models.TextField(_("安装包描述"))
description_en = models.TextField(_("英文插件描述"), null=True, blank=True)
category = models.CharField(_("所属范围"), max_length=32, choices=constants.CATEGORY_CHOICES)

class Meta:
verbose_name = _("Gse包描述(GsePackageDesc)")
verbose_name_plural = _("Gse包描述(GsePackageDesc)")
10 changes: 10 additions & 0 deletions apps/node_man/permissions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available.
Copyright (C) 2017-2022 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 https://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.
"""
27 changes: 27 additions & 0 deletions apps/node_man/permissions/package_manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available.
Copyright (C) 2017-2022 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 https://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 permissions

from apps.node_man.handlers.iam import IamHandler
from apps.utils.local import get_request_username


class PackageManagePermission(permissions.BasePermission):
message = _("您没有该操作的权限")

def has_permission(self, request, view):

if IamHandler().is_superuser(get_request_username()):
return True

return False
121 changes: 121 additions & 0 deletions apps/node_man/serializers/package_manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available.
Copyright (C) 2017-2022 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 https://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

from apps.exceptions import ValidationError
from apps.node_man.constants import GsePackageCode


class TagsSerializer(serializers.Serializer):
id = serializers.CharField()
name = serializers.CharField()
children = serializers.ListField()


class ConditionsSerializer(serializers.Serializer):
key = serializers.ChoiceField(choices=["version", "os_cpu_arch", "tags", "is_ready"])
values = serializers.ListField()


class PackageSerializer(serializers.Serializer):
id = serializers.IntegerField()
pkg_name = serializers.CharField()
version = serializers.CharField()
os = serializers.CharField()
cpu_arch = serializers.CharField()
tags = TagsSerializer(many=True)
creator = serializers.CharField()
pkg_ctime = serializers.DateTimeField()
is_ready = serializers.BooleanField()


class PackageDescSerializer(serializers.Serializer):
id = serializers.IntegerField()
version = serializers.CharField()
tags = TagsSerializer(many=True)
packages = PackageSerializer(many=True)
is_ready = serializers.BooleanField()


class ListResponseSerializer(serializers.Serializer):
total = serializers.IntegerField()
list = PackageSerializer(many=True)


class PackageDescResponseSerialiaer(serializers.Serializer):
total = serializers.IntegerField()
list = PackageDescSerializer(many=True)


class OperateSerializer(serializers.Serializer):
is_ready = serializers.BooleanField()


class QuickSearchSerializer(serializers.Serializer):
project = serializers.ChoiceField(choices=GsePackageCode.list_choices())


# TODO 与plugin相同可抽取公共Serializer
class UploadSerializer(serializers.Serializer):
class PkgFileField(serializers.FileField):
def to_internal_value(self, data):
data = super().to_internal_value(data)
file_name = data.name
if not (file_name.endswith(".tgz") or file_name.endswith(".tar.gz")):
raise ValidationError(_("仅支持'tgz', 'tar.gz'拓展名的文件"))
return data

module = serializers.ChoiceField(choices=["gse_agent", "gse_proxy"], required=False, default="gse_agent")
package_file = PkgFileField()


class UploadResponseSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
pkg_size = serializers.IntegerField()


class ParseSerializer(serializers.Serializer):
file_name = serializers.CharField()


class ParseResponseSerializer(serializers.Serializer):
class ParsePackageSerializer(serializers.Serializer):
module = serializers.ChoiceField(choices=["agent", "proxy"])
pkg_name = serializers.CharField()
pkg_abs_path = serializers.CharField()
version = serializers.CharField()
os = serializers.CharField()
cpu_arch = serializers.CharField()
config_templates = serializers.ListField()

description = serializers.CharField()
packages = ParsePackageSerializer(many=True)


class AgentRegisterSerializer(serializers.Serializer):
class RegisterPackageSerializer(serializers.Serializer):
pkg_abs_path = serializers.CharField()
tags = serializers.ListField()

is_release = serializers.BooleanField()
packages = RegisterPackageSerializer(many=True)


class AgentRegisterTaskSerializer(serializers.Serializer):
job_id = serializers.IntegerField()


class AgentRegisterTaskResponseSerializer(serializers.Serializer):
is_finish = serializers.BooleanField()
status = serializers.ChoiceField(choices=["SUCCESS", "FAILED", "RUNNING"])
message = serializers.CharField()
5 changes: 5 additions & 0 deletions apps/node_man/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
)
from apps.node_man.views.healthz import HealthzViewSet
from apps.node_man.views.host_v2 import HostV2ViewSet
from apps.node_man.views.package_manage import ( # AgentPackageDescViewSet,
PackageManageViewSet,
)
from apps.node_man.views.plugin import GsePluginViewSet
from apps.node_man.views.plugin_v2 import PluginV2ViewSet
from apps.node_man.views.sync_task import SyncTaskViewSet
Expand Down Expand Up @@ -67,6 +70,8 @@
router.register(r"v2/plugin", PluginV2ViewSet, basename="plugin_v2")
router.register(r"healthz", HealthzViewSet, basename="healthz")
router.register(r"sync_task", SyncTaskViewSet, basename="sync_task")
router.register(r"agent/package", PackageManageViewSet, basename="package_manage")
# router.register(r"agent/package_desc", AgentPackageDescViewSet, basename="package_desc")

biz_dispatcher = DjangoBasicResourceApiDispatcher(iam, settings.BK_IAM_SYSTEM_ID)
biz_dispatcher.register("biz", BusinessResourceProvider())
Expand Down
Loading

0 comments on commit 4f30709

Please sign in to comment.