diff --git a/apps/backend/agent/artifact_builder/base.py b/apps/backend/agent/artifact_builder/base.py index 8bcb8e8c72..c757a85051 100644 --- a/apps/backend/agent/artifact_builder/base.py +++ b/apps/backend/agent/artifact_builder/base.py @@ -26,6 +26,7 @@ from apps.core.tag.constants import AGENT_NAME_TARGET_ID_MAP, TargetType from apps.core.tag.handlers import TagHandler from apps.node_man import constants, models +from apps.node_man.models import GsePackageDesc, GsePackages from apps.utils import cache, files logger = logging.getLogger("app") @@ -517,13 +518,28 @@ 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: :return: """ - pass + for package_info in package_infos: + GsePackages.objects.update_or_create( + pkg_name=package_info["package_upload_info"]["pkg_name"], + version=package_info["artifact_meta_info"]["version"], + project=self.NAME, + pkg_size=package_info["package_upload_info"]["pkg_size"], + pkg_path=package_info["package_upload_info"]["pkg_path"], + md5=package_info["package_upload_info"]["md5"], + os=package_info["package_dir_info"]["os"], + cpu_arch=package_info["package_dir_info"]["cpu_arch"], + ) + + GsePackageDesc.objects.update_or_create( + id=AGENT_NAME_TARGET_ID_MAP[self.NAME], + project=self.NAME, + ) def get_artifact_meta_info(self, extract_dir: str) -> typing.Dict[str, typing.Any]: """ diff --git a/apps/node_man/constants.py b/apps/node_man/constants.py index 4905c79726..5aecf495ff 100644 --- a/apps/node_man/constants.py +++ b/apps/node_man/constants.py @@ -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" @@ -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] = ["稳定版本", "最新版本", "测试版本"] diff --git a/apps/node_man/handlers/meta.py b/apps/node_man/handlers/meta.py index 42fe4875f5..5a96ceea1b 100644 --- a/apps/node_man/handlers/meta.py +++ b/apps/node_man/handlers/meta.py @@ -20,6 +20,8 @@ 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.serializers.package_manage import FilterConditionPackageSerializer from apps.utils import APIModel @@ -471,28 +473,61 @@ 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}, - ] + 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): diff --git a/apps/node_man/migrations/0078_gsepackagedesc_gsepackages.py b/apps/node_man/migrations/0078_gsepackagedesc_gsepackages.py new file mode 100644 index 0000000000..d42babdd59 --- /dev/null +++ b/apps/node_man/migrations/0078_gsepackagedesc_gsepackages.py @@ -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)', + }, + ), + ] diff --git a/apps/node_man/serializers/package_manage.py b/apps/node_man/serializers/package_manage.py index 177f6c6445..300fab5584 100644 --- a/apps/node_man/serializers/package_manage.py +++ b/apps/node_man/serializers/package_manage.py @@ -11,14 +11,21 @@ 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): @@ -26,18 +33,70 @@ class ConditionsSerializer(serializers.Serializer): 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() @@ -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()) diff --git a/apps/node_man/tests/test_views/test_package_manage_views.py b/apps/node_man/tests/test_views/test_package_manage_views.py new file mode 100644 index 0000000000..898b5d50c5 --- /dev/null +++ b/apps/node_man/tests/test_views/test_package_manage_views.py @@ -0,0 +1,180 @@ +# -*- 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 apps.backend.tests.subscription.agent_adapter.test_adapter import ( + Proxy2StepAdapterTestCase, +) +from apps.node_man.handlers.meta import MetaHandler +from apps.node_man.models import GsePackages +from apps.node_man.tests.utils import create_gse_package + + +class PackageManageViewsTestCase(Proxy2StepAdapterTestCase): + def setUp(self): + super().setUp() + with self.ARTIFACT_BUILDER_CLASS(initial_artifact_path=self.ARCHIVE_PATH, tags=["tag1", "tag2"]) as builder: + builder.make() + + @classmethod + def clear_agent_data(cls): + pass + + def test_list(self): + # 和之前的builder.make加起来100 + create_gse_package(99) + + result = self.client.get(path="/api/agent/package/") + self.assertEqual(result["result"], True) + self.assertEqual(result["data"]["total"], 100) + self.assertEqual(len(result["data"]["list"]), 100) + + result = self.client.get(path="/api/agent/package/", data={"page": 1, "pagesize": 2}) + self.assertEqual(result["result"], True) + self.assertEqual(result["data"]["total"], 100) + self.assertEqual(len(result["data"]["list"]), 2) + + def test_list_with_filter_condition(self): + # 不筛选 + result = self.client.get(path="/api/agent/package/") + self.assertEqual(result["data"]["total"], 1) + self.assertEqual(len(result["data"]["list"]), 1) + + # 筛选tags + result = self.client.get(path="/api/agent/package/", data={"tags": "tag1"}) + self.assertEqual(len(result["data"]["list"]), 1) + self.assertIn("tag1", self.collect_all_tag_names(result["data"]["list"][0]["tags"])) + result = self.client.get(path="/api/agent/package/", data={"tags": "tag2"}) + self.assertEqual(len(result["data"]["list"]), 1) + self.assertIn("tag2", self.collect_all_tag_names(result["data"]["list"][0]["tags"])) + result = self.client.get(path="/api/agent/package/", data={"tags": "tag3"}) + self.assertEqual(len(result["data"]["list"]), 0) + + # 筛选os_cpu_arch + os, cpu_arch = "linux", "x86_64" + result = self.client.get(path="/api/agent/package/", data={"os_cpu_arch": f"{os}_{cpu_arch}"}) + self.assertEqual(len(result["data"]["list"]), 1) + self.assertEqual(result["data"]["list"][0]["os"], os) + self.assertEqual(result["data"]["list"][0]["cpu_arch"], cpu_arch) + result = self.client.get(path="/api/agent/package/", data={"os_cpu_arch": "windows_x86_64"}) + self.assertEqual(len(result["data"]["list"]), 0) + + # 筛选created_by + result = self.client.get(path="/api/agent/package/", data={"created_by": "admin"}) + self.assertEqual(len(result["data"]["list"]), 1) + self.assertEqual(result["data"]["list"][0]["created_by"], "admin") + result = self.client.get(path="/api/agent/package/", data={"created_by": "system"}) + self.assertEqual(len(result["data"]["list"]), 0) + + # 筛选is_ready + result = self.client.get(path="/api/agent/package/", data={"is_ready": "true"}) + self.assertEqual(len(result["data"]["list"]), 1) + self.assertEqual(result["data"]["list"][0]["is_ready"], True) + result = self.client.get(path="/api/agent/package/", data={"is_ready": "false"}) + self.assertEqual(len(result["data"]["list"]), 0) + + # 筛选version + result = self.client.get(path="/api/agent/package/", data={"version": "1.0.1"}) + self.assertEqual(len(result["data"]["list"]), 1) + self.assertEqual(result["data"]["list"][0]["version"], "1.0.1") + result = self.client.get(path="/api/agent/package/", data={"version": "1.0.2"}) + self.assertEqual(len(result["data"]["list"]), 0) + + @classmethod + def collect_all_tag_names(cls, tags): + """ + tags: [ + { + "id": "builtin", + "name": "内置标签", + "children": [ + {"id": "stable", "name": "稳定版本", "children": []}, + {"id": "latest", "name": "最新版本", "children": []}, + ], + }, + { + "id": "custom", + "name": "自定义标签", + "children": [ + {"id": "custom", "name": "自定义版本", "children": []} + ] + }, + ] + """ + tag_name_set = set() + for parent_tag in tags: + for children_tag in parent_tag["children"]: + tag_name_set.add(children_tag["name"]) + + return tag_name_set + + def test_update(self): + first_gse_package = GsePackages.objects.first() + self.assertEqual(first_gse_package.is_ready, True) + self.client.put(path=f"/api/agent/package/{first_gse_package.id}/", data={"is_ready": False}) + self.assertEqual(GsePackages.objects.first().is_ready, False) + + def test_destroy(self): + gse_packages = GsePackages.objects.all() + self.assertEqual(len(gse_packages), 1) + self.client.delete(path=f"/api/agent/package/{gse_packages.first().id}/") + self.assertEqual(len(GsePackages.objects.all()), 0) + + def test_quick_search_condition(self): + result = self.client.get(path="/api/agent/package/quick_search_condition/") + for condition in result["data"]: + if condition["id"] == "os_cpu_arch": + self.assertCountEqual( + condition["children"], + [ + {"id": "ALL", "name": "ALL", "count": 1}, + {"id": "linux_x86_64", "name": "linux_x86_64", "count": 1}, + ], + ) + elif condition["id"] == "version": + self.assertCountEqual( + condition["children"], + [ + {"id": "ALL", "name": "ALL", "count": 1}, + {"id": "1.0.1", "name": "1.0.1", "count": 1}, + ], + ) + + def test_filter_condition_with_agent_pkg_manage(self): + result = MetaHandler().filter_condition("agent_pkg_manage") + for condition in result: + if condition["id"] == "version": + self.assertCountEqual( + condition["children"], + [ + {"id": "1.0.1", "name": "1.0.1"}, + ], + ) + elif condition["id"] == "tags": + self.assertCountEqual( + condition["children"], + [ + {"id": "tag1", "name": ""}, + {"id": "tag2", "name": ""}, + ], + ) + elif condition["id"] == "creator": + self.assertCountEqual( + condition["children"], + [ + {"id": "admin", "name": "admin"}, + ], + ) + elif condition["id"] == "is_ready": + self.assertCountEqual( + condition["children"], + [ + {"id": True, "name": "启用"}, + ], + ) diff --git a/apps/node_man/tests/utils.py b/apps/node_man/tests/utils.py index bab618de80..6a944ead40 100644 --- a/apps/node_man/tests/utils.py +++ b/apps/node_man/tests/utils.py @@ -27,6 +27,7 @@ from apps.node_man.models import ( AccessPoint, Cloud, + GsePackages, Host, IdentityData, InstallChannel, @@ -677,8 +678,9 @@ def create_ap(number): def create_job(number, id=None, end_time=None, bk_biz_scope=None, task_id_list=None, created_by=None): job_types = list(chain(*list(constants.JOB_TYPE_MAP.values()))) job_types = [ - job_type for job_type in job_types if - tools.JobTools.unzip_job_type(job_type)["op_type"] in constants.OP_TYPE_TUPLE + job_type + for job_type in job_types + if tools.JobTools.unzip_job_type(job_type)["op_type"] in constants.OP_TYPE_TUPLE ] if bk_biz_scope == {} or bk_biz_scope: @@ -1233,3 +1235,40 @@ def ret_to_validate_data(data): login_ip_info = HostHandler.get_host_infos_gby_ip_key(login_ips, constants.CmdbIpVersion.V4.value) return biz_info, data, cloud_info, ap_id_name, inner_ip_info, outer_ip_info, login_ip_info, bk_biz_scope + + +def create_gse_package( + number, + start_id=1, + version=None, + project=None, + pkg_size=None, + is_ready=None, + pkg_path=None, + location=None, + created_by=None, + os=None, + cpu_arch=None, +): + gse_packages = [] + for i in range(start_id, number + start_id): + gse_package = GsePackages( + id=i, + pkg_name=f"pkg_name{i+1}", + version=version or random.choice(["version1", "version2", "version3"]), + project=project or random.choice(["agent", "proxy"]), + pkg_size=pkg_size or random.randint(1, 2000), + pkg_path=pkg_path or "/tmp/", + md5="", + location=location or "", + os=os or random.choice(constants.OS_TUPLE), + cpu_arch=cpu_arch or random.choice(constants.CPU_TUPLE), + is_ready=is_ready or random.choice(list(constants.GSE_PACKAGE_ENABLE_ALIAS_MAP.keys())), + created_by=created_by or "admin", + updated_by=created_by or "admin", + version_log="", + version_log_en="", + ) + gse_packages.append(gse_package) + gse_packages = GsePackages.objects.bulk_create(gse_packages) + return [gse_package.id for gse_package in gse_packages] diff --git a/apps/node_man/utils/filters.py b/apps/node_man/utils/filters.py index 722985b0a4..2c90d3441f 100644 --- a/apps/node_man/utils/filters.py +++ b/apps/node_man/utils/filters.py @@ -8,22 +8,66 @@ 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. """ - import django_filters from django_filters.rest_framework import FilterSet from apps.node_man.models import GsePackages +from apps.node_man.serializers import package_manage as pkg_manage class GsePackageFilter(FilterSet): - tags = django_filters.CharFilter(name="tags", method="filter_tags") - os_cpu_arch = django_filters.CharFilter(name="os_cpu_arch", method="filter_os_cpu_arch") + tags = django_filters.CharFilter(field_name="tags", method="filter_tags") + os_cpu_arch = django_filters.CharFilter(field_name="os_cpu_arch", method="filter_os_cpu_arch") + project = django_filters.CharFilter(field_name="project", method="filter_project") + created_by = django_filters.CharFilter(field_name="created_by", method="filter_created_by") + is_ready = django_filters.CharFilter(field_name="is_ready", method="filter_is_ready") + version = django_filters.CharFilter(field_name="version", method="filter_version") def filter_tags(self, queryset, name, value): - pass + tag_names = self.str_to_list(value) + package_ids = [ + gse_package.get("id") + for tag_name in tag_names + for gse_package in pkg_manage.PackageSerializer(queryset, many=True).data + if tag_name + in set( + children_tag.get("name") + for parent_tag in gse_package.get("tags") + for children_tag in parent_tag["children"] + ) + ] + + return queryset.filter(id__in=package_ids) def filter_os_cpu_arch(self, queryset, name, value): - pass + os_cpu_arch_list = self.str_to_list(value) + for os_cpu_arch in os_cpu_arch_list: + os_cpu_arch = os_cpu_arch.split("_", 1) + if len(os_cpu_arch) != 2: + continue + + os, cpu_arch = os_cpu_arch[0], os_cpu_arch[1] + queryset = queryset.filter(os=os, cpu_arch=cpu_arch) + return queryset + + def filter_project(self, queryset, name, value): + return queryset.filter(project__in=self.str_to_list(value)) + + def filter_created_by(self, queryset, name, value): + return queryset.filter(created_by__in=self.str_to_list(value)) + + def filter_is_ready(self, queryset, name, value): + return queryset.filter(is_ready__in=self.str_to_list(value, excepted_type="bool")) + + def filter_version(self, queryset, name, value): + return queryset.filter(version__in=self.str_to_list(value)) + + @classmethod + def str_to_list(cls, s, excepted_type="str"): + li = [tag_name.replace(" ", "").strip() for tag_name in s.split(",")] + if excepted_type == "bool": + li = [True if v.lower() == "true" else False for v in li] + return li class Meta: model = GsePackages diff --git a/apps/node_man/views/package_manage.py b/apps/node_man/views/package_manage.py index b4793495e7..c245c7566d 100644 --- a/apps/node_man/views/package_manage.py +++ b/apps/node_man/views/package_manage.py @@ -8,6 +8,8 @@ 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 collections import defaultdict + from django_filters.rest_framework import DjangoFilterBackend from drf_yasg import openapi from rest_framework import filters @@ -35,41 +37,24 @@ class PackageManageViewSet(ModelViewSet): filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) filter_class = GsePackageFilter + def get_serializer_class(self): + if self.action == "update": + return pkg_manage.OperateSerializer + + return self.serializer_class + @swagger_auto_schema( responses={200: pkg_manage.ListResponseSerializer}, operation_summary="安装包列表", tags=PACKAGE_MANAGE_VIEW_TAGS, ) def list(self, request, *args, **kwargs): - mock_data = { - "total": 2, - "list": [ - { - "id": 1, - "pkg_name": "pkg_name", - "version": "1.1.1", - "os": "Linux", - "cpu_arch": "x86_64", - "tags": [{"id": "stable", "name": "稳定版本"}], - "creator": "string", - "pkg_ctime": "2019-08-24 14:15:22", - "is_ready": True, - }, - { - "id": 2, - "pkg_name": "pkg_name", - "version": "1.1.2", - "os": "Linux", - "os_cpu_arch": "x86_64", - "tags": [{"id": "stable", "name": "稳定版本"}], - "creator": "string", - "pkg_ctime": "2019-08-24 14:15:22", - "is_ready": True, - }, - ], + queryset = self.filter_queryset(self.queryset) + data = { + "total": queryset.count(), + "list": self.paginate_queryset(queryset) if "pagesize" in request.query_params else queryset, } - return Response(mock_data) - # return super().list(request, *args, **kwargs) + return Response(pkg_manage.ListResponseSerializer(data).data) @swagger_auto_schema( operation_summary="操作类动作:启用/停用", @@ -78,27 +63,22 @@ def list(self, request, *args, **kwargs): tags=PACKAGE_MANAGE_VIEW_TAGS, ) def update(self, request, validated_data, *args, **kwargs): - mock_data = { - "id": 1, - "pkg_name": "pkg_name", - "version": "1.1.1", - "os": "Linux", - "cpu_arch": "x86_64", - "tags": [{"id": "stable", "name": "稳定版本"}], - "creator": "string", - "pkg_ctime": "2019-08-24 14:15:22", - "is_ready": True, - } + instance = self.get_object() + serializer = self.get_serializer(instance, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + self.perform_update(serializer) - return Response(mock_data) + updated_instance = self.get_object() + + return Response(pkg_manage.PackageSerializer(updated_instance).data) @swagger_auto_schema( operation_summary="删除安装包", tags=PACKAGE_MANAGE_VIEW_TAGS, ) def destroy(self, request, *args, **kwargs): - - return Response() + # return Response() + return super(PackageManageViewSet, self).destroy(request, *args, **kwargs) @swagger_auto_schema( operation_summary="获取快速筛选信息", @@ -111,24 +91,52 @@ def destroy(self, request, *args, **kwargs): ) @action(detail=False, methods=["GET"]) def quick_search_condition(self, request, *args, **kwargs): - mock_version = [ - {"name": "2.1.8", "id": "2.1.8", "count": 10}, - {"name": "2.1.7", "id": "2.1.7", "count": 10}, - {"name": "ALL", "id": "all", "count": 20}, - ] + gse_packages = pkg_manage.QuickFilterConditionPackageSerializer(self.queryset, many=True).data + version_set, os_cpu_arch_set = set(), set() + version_count_map, os_cpu_arch_count_map = defaultdict(int), defaultdict(int) + if gse_packages: + for gse_package in gse_packages: + version, os, cpu_arch = gse_package.get("version"), gse_package.get("os"), gse_package.get("cpu_arch") + os_cpu_arch = f"{os}_{cpu_arch}" - mock_os_cpu_arch = [ - {"name": "Linux_x86_64", "id": "linux_x86_64", "count": 10}, - {"name": "Linux_x86", "id": "linux_x86", "count": 10}, - {"name": "ALL", "id": "all", "count": 20}, - ] + version_set.add(version) + os_cpu_arch_set.add(os_cpu_arch) - mock_data = [ - {"name": "操作系统/架构", "id": "os_cpu_arch", "children": mock_os_cpu_arch}, - {"name": "版本号", "id": "version", "children": mock_version}, - ] + version_count_map[version] += 1 + version_count_map["ALL"] += 1 + os_cpu_arch_count_map[os_cpu_arch] += 1 + os_cpu_arch_count_map["ALL"] += 1 + version_set.add("ALL") + os_cpu_arch_set.add("ALL") - return Response(mock_data) + return Response( + [ + { + "name": "操作系统/架构", + "id": "os_cpu_arch", + "children": [ + { + "id": os_cpu_arch, + "name": os_cpu_arch, + "count": os_cpu_arch_count_map[os_cpu_arch], + } + for os_cpu_arch in os_cpu_arch_set + ], + }, + { + "name": "版本号", + "id": "version", + "children": [ + { + "id": version, + "name": version, + "count": version_count_map[version], + } + for version in version_set + ], + }, + ] + ) @swagger_auto_schema( operation_summary="Agent包上传", @@ -209,16 +217,34 @@ def query_register_task(self, request, validated_data): @action(detail=False, methods=["GET"]) def tags(self, request): # 由tag handler 实现 + # mock_data = [ + # { + # "id": "builtin", + # "name": "内置标签", + # "children": [ + # {"id": "stable", "name": "稳定版本", "children": []}, + # {"id": "latest", "name": "最新版本", "children": []}, + # ], + # }, + # { + # "id": "custom", + # "name": "自定义标签", + # "children": [ + # {"id": "custom", "name": "自定义版本", "children": []} + # ] + # }, + # ] mock_data = [ { "id": "builtin", "name": "内置标签", - "children": [ - {"id": "stable", "name": "稳定版本", "children": []}, - {"id": "latest", "name": "最新版本", "children": []}, - ], + "children": [], + }, + { + "id": "custom", + "name": "自定义标签", + "children": [], }, - {"id": "custom", "name": "自定义标签", "children": [{"id": "custom", "name": "自定义版本", "children": []}]}, ] return Response(mock_data)