Skip to content

Commit

Permalink
feature: 提供 Agent 包管理后台接口 (closed TencentBlueKing#1683)
Browse files Browse the repository at this point in the history
  • Loading branch information
ping15 committed Nov 27, 2023
1 parent 24965a0 commit ab325f6
Show file tree
Hide file tree
Showing 10 changed files with 707 additions and 131 deletions.
2 changes: 1 addition & 1 deletion apps/backend/agent/artifact_builder/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ def update_or_create_support_files(self, package_infos: typing.List[typing.Dict]
agent_name=self.NAME,
)

def update_or_create_package_records(self, v):
def update_or_create_package_records(self, package_infos):
"""
创建或更新安装包记录,待 Agent 包管理完善
:param package_infos:
Expand Down
8 changes: 8 additions & 0 deletions apps/node_man/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,11 @@ def get_optional_items(cls) -> List[str]:
IAM_ACTION_CHOICES = tuple_choices(IAM_ACTION_TUPLE)
IamActionType = choices_to_namedtuple(IAM_ACTION_CHOICES)

GSE_PACKAGE_ENABLE_ALIAS_MAP = {
True: _("启用"),
False: _("禁用"),
}


class SubscriptionType:
POLICY = "policy"
Expand Down Expand Up @@ -1136,3 +1141,6 @@ class CommonExecutionSolutionStepType(EnhanceEnum):
@classmethod
def _get_member__alias_map(cls) -> Dict[Enum, str]:
return {cls.DEPENDENCIES: _("依赖文件"), cls.COMMANDS: _("命令")}


BUILT_IN_TAG_NAMES: List[str] = ["稳定版本", "最新版本", "测试版本"]
10 changes: 10 additions & 0 deletions apps/node_man/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,13 @@ class YunTiPolicyConfigNotExistsError(NodeManBaseException):
MESSAGE = _("云梯策略配置不存在")
MESSAGE_TPL = _("云梯策略配置不存在")
ERROR_CODE = 43


class FileDoesNotExistError(NodeManBaseException):
MESSAGE = _("文件不存在")
ERROR_CODE = 44


class PluginParseError(NodeManBaseException):
MESSAGE = _("插件解析错误")
ERROR_CODE = 45
79 changes: 59 additions & 20 deletions apps/node_man/handlers/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
from apps.node_man.handlers.cloud import CloudHandler
from apps.node_man.handlers.cmdb import CmdbHandler
from apps.node_man.handlers.install_channel import InstallChannelHandler
from apps.node_man.models import GsePackages
from apps.node_man.permissions.package_manage import PackageManagePermission
from apps.node_man.serializers.package_manage import FilterConditionPackageSerializer
from apps.utils import APIModel


Expand Down Expand Up @@ -471,28 +474,64 @@ def fetch_os_type_children(os_types: Tuple = constants.OsType):

@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},
]
if not PackageManagePermission().has_permission(None, None):
return {"message": "该用户不是管理员"}

version_set, tag_name_description_map, creator_set, is_ready_set = set(), dict(), set(), set()
gse_packages = FilterConditionPackageSerializer(GsePackages.objects.all(), many=True).data
for gse_package in gse_packages:
version_set.add(gse_package.get("version"))
for parent_tag in gse_package.get("tags"):
for child_tag in parent_tag.get("children"):
tag_name_description_map[child_tag.get("name")] = child_tag.get("description")
creator_set.add(gse_package.get("created_by"))
is_ready_set.add(gse_package.get("is_ready"))

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},
{
"name": _("版本号"),
"id": "version",
"children": [
{
"id": version,
"name": version,
}
for version in version_set
],
},
{
"name": _("标签信息"),
"id": "tags",
"children": [
{
"id": tag_name,
"name": tag_description,
}
for tag_name, tag_description in tag_name_description_map.items()
],
},
{
"name": _("上传用户"),
"id": "creator",
"children": [
{
"id": creator_name,
"name": creator_name,
}
for creator_name in creator_set
],
},
{
"name": _("状态"),
"id": "is_ready",
"children": [
{
"id": is_ready,
"name": constants.GSE_PACKAGE_ENABLE_ALIAS_MAP.get(is_ready, is_ready),
}
for is_ready in is_ready_set
],
},
]

def filter_condition(self, category):
Expand Down
57 changes: 57 additions & 0 deletions apps/node_man/migrations/0078_gsepackagedesc_gsepackages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Generated by Django 3.2.4 on 2023-11-10 09:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('node_man', '0077_auto_20231029_1336'),
]

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)',
},
),
]
97 changes: 85 additions & 12 deletions apps/node_man/serializers/package_manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,92 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers

from apps.core.tag.models import Tag
from apps.exceptions import ValidationError
from apps.node_man.constants import GsePackageCode
from apps.node_man.constants import BUILT_IN_TAG_NAMES, GsePackageCode
from apps.node_man.models import GsePackageDesc


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


class ParentTagSerializer(serializers.Serializer):
name = serializers.CharField()
description = serializers.CharField()
children = TagsSerializer(many=True)


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


class PackageSerializer(serializers.Serializer):
class BasePackageSerializer(serializers.Serializer):
def get_tags(self, obj):
agent_project_ids = GsePackageDesc.objects.filter(project=obj.project).values_list("id", flat=True)
tags = Tag.objects.filter(target_id__in=agent_project_ids, target_version=obj.version).values_list(
"name", "description"
)

mock_built_in_tags, mock_custom_tags = self.split_builtin_tags_and_custom_tags(tags)
mock_data = [
{
"name": "builtin",
"description": "内置标签",
"children": mock_built_in_tags,
},
{"name": "custom", "description": "自定义标签", "children": mock_custom_tags},
]
self.filter_no_children_parent_tag(mock_data)
return ParentTagSerializer(mock_data, many=True).data

@classmethod
def split_builtin_tags_and_custom_tags(cls, tags):
"""将标签拆分为内置的和自定义的"""
built_in_tags, custom_tags = [], []
for name, description in tags:
if name in BUILT_IN_TAG_NAMES:
built_in_tags.append({"name": name, "description": description})
else:
custom_tags.append({"name": name, "description": description})

return built_in_tags, custom_tags

@classmethod
def filter_no_children_parent_tag(cls, parent_tags):
for i in range(len(parent_tags) - 1, -1, -1):
if not parent_tags[i].get("children"):
parent_tags.pop(i)


class PackageSerializer(BasePackageSerializer):
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()
# tags = TagsSerializer(many=True)
tags = serializers.SerializerMethodField()
created_by = serializers.CharField()
created_time = serializers.DateTimeField()
is_ready = serializers.BooleanField()


class FilterConditionPackageSerializer(BasePackageSerializer):
version = serializers.CharField()
tags = serializers.SerializerMethodField()
created_by = serializers.CharField()
is_ready = serializers.BooleanField()


class QuickFilterConditionPackageSerializer(BasePackageSerializer):
version = serializers.CharField()
os = serializers.CharField()
cpu_arch = serializers.CharField()


class PackageDescSerializer(serializers.Serializer):
id = serializers.IntegerField()
version = serializers.CharField()
Expand All @@ -59,6 +118,13 @@ class PackageDescResponseSerialiaer(serializers.Serializer):
class OperateSerializer(serializers.Serializer):
is_ready = serializers.BooleanField()

def update(self, instance, validated_data):
for attr, value in validated_data.items():
setattr(instance, attr, value)

instance.save()
return instance


class QuickSearchSerializer(serializers.Serializer):
project = serializers.ChoiceField(choices=GsePackageCode.list_choices())
Expand Down Expand Up @@ -90,13 +156,20 @@ class ParseSerializer(serializers.Serializer):

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()
project = serializers.ChoiceField(choices=["agent", "proxy"], required=False)
pkg_name = serializers.CharField(required=False)
pkg_abs_path = serializers.CharField(source="pkg_absolute_path")
version = serializers.CharField(required=False)
os = serializers.CharField()
cpu_arch = serializers.CharField()
config_templates = serializers.ListField()
config_templates = serializers.ListField(default=[])

def to_representation(self, instance):
data = super().to_representation(instance)
data["project"] = self.context.get("project", "")
data["pkg_name"] = self.context.get("pkg_name", "")
data["version"] = self.context.get("version", "")
return data

description = serializers.CharField()
packages = ParsePackageSerializer(many=True)
Expand Down
Loading

0 comments on commit ab325f6

Please sign in to comment.