From 747f95afa25871bf8d934234ba01f959c579be5a Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Wed, 4 Dec 2024 20:01:42 +0800 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=E6=B5=81=E7=A8=8B=E5=B8=82?= =?UTF-8?q?=E5=9C=BA=E5=85=B1=E4=BA=AB=E6=A8=A1=E6=9D=BF=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/default.py | 7 ++ config/urls_custom.py | 10 +- env.py | 5 + gcloud/contrib/templatemaker/__init__.py | 0 gcloud/contrib/templatemaker/admin.py | 23 +++++ gcloud/contrib/templatemaker/apis/__init__.py | 0 .../templatemaker/apis/django/__init__.py | 0 .../contrib/templatemaker/apis/django/api.py | 98 +++++++++++++++++++ .../templatemaker/apis/django/validators.py | 33 +++++++ .../templatemaker/apis/drf/__init__.py | 0 .../apis/drf/viewsets/__init__.py | 14 +++ .../apis/drf/viewsets/template_marker.py | 57 +++++++++++ gcloud/contrib/templatemaker/apps.py | 6 ++ .../templatemaker/migrations/0001_initial.py | 37 +++++++ .../templatemaker/migrations/__init__.py | 0 gcloud/contrib/templatemaker/models.py | 31 ++++++ gcloud/contrib/templatemaker/urls.py | 26 +++++ 17 files changed, 342 insertions(+), 5 deletions(-) create mode 100644 gcloud/contrib/templatemaker/__init__.py create mode 100644 gcloud/contrib/templatemaker/admin.py create mode 100644 gcloud/contrib/templatemaker/apis/__init__.py create mode 100644 gcloud/contrib/templatemaker/apis/django/__init__.py create mode 100644 gcloud/contrib/templatemaker/apis/django/api.py create mode 100644 gcloud/contrib/templatemaker/apis/django/validators.py create mode 100644 gcloud/contrib/templatemaker/apis/drf/__init__.py create mode 100644 gcloud/contrib/templatemaker/apis/drf/viewsets/__init__.py create mode 100644 gcloud/contrib/templatemaker/apis/drf/viewsets/template_marker.py create mode 100644 gcloud/contrib/templatemaker/apps.py create mode 100644 gcloud/contrib/templatemaker/migrations/0001_initial.py create mode 100644 gcloud/contrib/templatemaker/migrations/__init__.py create mode 100644 gcloud/contrib/templatemaker/models.py create mode 100644 gcloud/contrib/templatemaker/urls.py diff --git a/config/default.py b/config/default.py index ba5c58e90a..ad04ebe8dc 100644 --- a/config/default.py +++ b/config/default.py @@ -67,6 +67,7 @@ "gcloud.contrib.develop", "gcloud.contrib.collection", "gcloud.contrib.operate_record", + "gcloud.contrib.templatemaker", "gcloud.apigw", "gcloud.common_template", "gcloud.label", @@ -883,3 +884,9 @@ def check_engine_admin_permission(request, *args, **kwargs): if "BKAPP_SOPS_BROKER_URL" in os.environ: BROKER_URL = os.getenv("BKAPP_SOPS_BROKER_URL") print(f"BROKER_URL: {BROKER_URL}") + + +# 共享模板开关 +ENABLE_FLOW_MARKET = env.ENABLE_FLOW_MARKET +# SRE 商店路由 +FLOW_MARKET_API_URL = env.FLOW_MARKET_API_URL diff --git a/config/urls_custom.py b/config/urls_custom.py index 6ed3c5ecef..8efd65a41f 100644 --- a/config/urls_custom.py +++ b/config/urls_custom.py @@ -10,12 +10,11 @@ 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 permissions -from drf_yasg.views import get_schema_view -from drf_yasg import openapi -from django.conf.urls import include, url from django.conf import settings - +from django.conf.urls import include, url +from drf_yasg import openapi +from drf_yasg.views import get_schema_view +from rest_framework import permissions # 用户自定义 urlconf urlpatterns_custom = [ @@ -41,6 +40,7 @@ url(r"^plugin_service/", include("plugin_service.urls")), url(r"^mako_operations/", include("gcloud.mako_template_helper.urls")), url(r"^engine_admin/", include("pipeline.contrib.engine_admin.urls")), + url(r"^template_maker/", include("gcloud.contrib.templatemaker.urls")), ] schema_view = get_schema_view( diff --git a/env.py b/env.py index 59c9045d14..2cd154eeae 100644 --- a/env.py +++ b/env.py @@ -153,3 +153,8 @@ # bk_audit BK_AUDIT_ENDPOINT = os.getenv("BK_AUDIT_ENDPOINT", None) BK_AUDIT_DATA_TOKEN = os.getenv("BK_AUDIT_DATA_TOKEN", None) + +# 共享模板开关 +ENABLE_FLOW_MARKET = False if os.getenv("BKAPP_ENABLE_FLOW_MARKET") is None else True +# SRE 商店路由 +FLOW_MARKET_API_URL = os.getenv("FLOW_MARKET_API_URL", "") diff --git a/gcloud/contrib/templatemaker/__init__.py b/gcloud/contrib/templatemaker/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gcloud/contrib/templatemaker/admin.py b/gcloud/contrib/templatemaker/admin.py new file mode 100644 index 0000000000..d9c3dc45a2 --- /dev/null +++ b/gcloud/contrib/templatemaker/admin.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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.contrib import admin + +from gcloud.contrib.templatemaker import models + + +@admin.register(models.TemplateSharedRecord) +class AppMakerAdmin(admin.ModelAdmin): + list_display = ["project_id", "template_id", "template_source", "create", "create_time"] + list_filter = ["project_id", "template_source", "create", "create_time"] + search_fields = ["project_id", "create"] diff --git a/gcloud/contrib/templatemaker/apis/__init__.py b/gcloud/contrib/templatemaker/apis/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gcloud/contrib/templatemaker/apis/django/__init__.py b/gcloud/contrib/templatemaker/apis/django/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gcloud/contrib/templatemaker/apis/django/api.py b/gcloud/contrib/templatemaker/apis/django/api.py new file mode 100644 index 0000000000..0f6f687d6a --- /dev/null +++ b/gcloud/contrib/templatemaker/apis/django/api.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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. +""" +import json +import requests +import logging + +from rest_framework.response import Response +from django.views.decorators.http import require_POST, require_GET +from django.http import JsonResponse + +from gcloud.conf import settings +from gcloud import err_code +from gcloud.contrib.templatemaker.models import TemplateSharedRecord +from gcloud.tasktmpl3.models import TaskTemplate +from gcloud.utils.decorators import request_validate +from gcloud.contrib.templatemaker.apis.django.validators import JsonValidator + + +def _get_market_routing(market_url): + return "{}/{}".format(settings.FLOW_MARKET_API_URL, market_url) + + +@require_GET +def get_template_market_details(request, template_id): + project_id = json.loads(request.body).get("project_id") + + url = _get_market_routing("market/details/") + + kwargs = { + "project_id": project_id, + } + + # 根据业务id和模板id从第三方接口获取模板详情 + result = requests.post(url, data=kwargs) + + if not result: + logging.exception("get market template details from third party fails") + return Response( + { + "result": False, + "message": "get market template details from third party fails", + "code": err_code.OPERATION_FAIL.code, + } + ) + + return True + + +@require_POST +@request_validate(JsonValidator) +def maker_template(request, template_id): + if not settings.ENABLE_FLOW_MARKET: + return False + + params = json.loads(request.body) + project_id = params.get("project_id") + try: + TaskTemplate.objects.filter(id=template_id, project__id=project_id).first() + except Exception as e: + logging.exception(e) + return Response( + {"result": False, "message": "template_id does not exist", "code": err_code.OPERATION_FAIL.code} + ) + + url = _get_market_routing("prod/api/") + + kwargs = None + + headers = None + + # 调用第三方接口 + result = requests.post(url, headers=headers, data=kwargs) + + if not result: + logging.exception("Sharing template to SRE store fails") + return JsonResponse( + {"result": False, "message": "Sharing template to SRE store fails", "code": err_code.OPERATION_FAIL.code} + ) + + record, created = TemplateSharedRecord.objects.get_or_create( + project_id=params["project_id"], + template_id=template_id, + defaults={"template_source": params["template_source"], "create": request.user.username}, + ) + if not created: + return JsonResponse({"result": False, "message": "Record already exists", "code": err_code.OPERATION_FAIL.code}) + + return JsonResponse({"result": True, "message": "shared success", "code": err_code.SUCCESS.code}) diff --git a/gcloud/contrib/templatemaker/apis/django/validators.py b/gcloud/contrib/templatemaker/apis/django/validators.py new file mode 100644 index 0000000000..af43e70e54 --- /dev/null +++ b/gcloud/contrib/templatemaker/apis/django/validators.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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. +""" + +import logging +import ujson as json + +from django.utils.translation import ugettext_lazy as _ + +from gcloud.utils.validate import RequestValidator + +logger = logging.getLogger("root") + + +class JsonValidator(RequestValidator): + def validate(self, request, *args, **kwargs): + try: + json.loads(request.body) + except Exception: + message = _("非法请求: 数据错误, 请求不是合法的Json格式 | validate") + logger.error(message) + return False, message + + return True, "" diff --git a/gcloud/contrib/templatemaker/apis/drf/__init__.py b/gcloud/contrib/templatemaker/apis/drf/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gcloud/contrib/templatemaker/apis/drf/viewsets/__init__.py b/gcloud/contrib/templatemaker/apis/drf/viewsets/__init__.py new file mode 100644 index 0000000000..ea4177dd98 --- /dev/null +++ b/gcloud/contrib/templatemaker/apis/drf/viewsets/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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 gcloud.contrib.templatemaker.apis.drf.viewsets.template_marker import TemplateMakerViewSet # noqa diff --git a/gcloud/contrib/templatemaker/apis/drf/viewsets/template_marker.py b/gcloud/contrib/templatemaker/apis/drf/viewsets/template_marker.py new file mode 100644 index 0000000000..6570b03130 --- /dev/null +++ b/gcloud/contrib/templatemaker/apis/drf/viewsets/template_marker.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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. +""" + +import logging + +from rest_framework.response import Response +from rest_framework import permissions + +from gcloud.contrib.templatemaker.models import TemplateSharedRecord +from gcloud.core.apis.drf.serilaziers.task_template import TaskTemplateSerializer, TaskTemplateListSerializer +from gcloud.taskflow3.models import TaskTemplate +from gcloud.label.models import TemplateLabelRelation +from gcloud.core.apis.drf.viewsets.base import GcloudModelViewSet + + +class HasValidTemplateID(permissions.BasePermission): + def has_permission(self, request, view): + template_id = view.kwargs.get("pk") + + if not template_id: + logging.warning("template_id is required.") + return False + try: + TemplateSharedRecord.objects.get(template_id=template_id) + except Exception: + logging.warning("template_id {} does not exist.".format(template_id)) + return False + return True + + +class TemplateMakerViewSet(GcloudModelViewSet): + queryset = TaskTemplate.objects.filter(pipeline_template__isnull=False, is_deleted=False) + permission_classes = [permissions.IsAuthenticated, HasValidTemplateID] + + def get_serializer_class(self): + if self.action == "list": + return TaskTemplateListSerializer + return TaskTemplateSerializer + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(instance) + data = serializer.data + labels = TemplateLabelRelation.objects.fetch_templates_labels([instance.id]).get(instance.id, []) + data["template_labels"] = [label["label_id"] for label in labels] + + return Response(data) diff --git a/gcloud/contrib/templatemaker/apps.py b/gcloud/contrib/templatemaker/apps.py new file mode 100644 index 0000000000..a5fcdbd722 --- /dev/null +++ b/gcloud/contrib/templatemaker/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TemplatemakerConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "gcloud.contrib.templatemaker" diff --git a/gcloud/contrib/templatemaker/migrations/0001_initial.py b/gcloud/contrib/templatemaker/migrations/0001_initial.py new file mode 100644 index 0000000000..9643d92d63 --- /dev/null +++ b/gcloud/contrib/templatemaker/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2.15 on 2024-12-04 10:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="TemplateSharedRecord", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("project_id", models.IntegerField(default=-1, help_text="项目 ID", verbose_name="项目 ID")), + ("template_id", models.IntegerField(db_index=True, verbose_name="模版ID")), + ( + "template_source", + models.CharField( + choices=[("project", "项目流程"), ("common", "公共流程"), ("onetime", "一次性任务")], + default="project", + max_length=32, + verbose_name="流程模板来源", + ), + ), + ("create", models.CharField(help_text="执行者", max_length=128, verbose_name="执行者")), + ("create_time", models.DateTimeField(auto_now_add=True, help_text="执行时间", verbose_name="执行时间")), + ], + options={ + "verbose_name": "模板共享记录 TemplateSharedRecord", + "verbose_name_plural": "模板共享记录 TemplateSharedRecord", + "unique_together": {("project_id", "template_id")}, + }, + ), + ] diff --git a/gcloud/contrib/templatemaker/migrations/__init__.py b/gcloud/contrib/templatemaker/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gcloud/contrib/templatemaker/models.py b/gcloud/contrib/templatemaker/models.py new file mode 100644 index 0000000000..f32dd63fc2 --- /dev/null +++ b/gcloud/contrib/templatemaker/models.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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.db import models +from django.utils.translation import ugettext_lazy as _ + +from gcloud.constants import TEMPLATE_SOURCE, PROJECT + + +class TemplateSharedRecord(models.Model): + project_id = models.IntegerField(_("项目 ID"), default=-1, help_text="项目 ID") + template_id = models.IntegerField(_("模版ID"), db_index=True) + template_source = models.CharField(_("流程模板来源"), max_length=32, choices=TEMPLATE_SOURCE, default=PROJECT) + create = models.CharField(_("执行者"), max_length=128, help_text="执行者") + create_time = models.DateTimeField(_("执行时间"), auto_now_add=True, help_text="执行时间") + + class Meta: + verbose_name = _("模板共享记录 TemplateSharedRecord") + verbose_name_plural = _("模板共享记录 TemplateSharedRecord") + unique_together = ("project_id", "template_id") diff --git a/gcloud/contrib/templatemaker/urls.py b/gcloud/contrib/templatemaker/urls.py new file mode 100644 index 0000000000..28bcb0d984 --- /dev/null +++ b/gcloud/contrib/templatemaker/urls.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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.conf.urls import include, url +from rest_framework.routers import DefaultRouter +from gcloud.contrib.templatemaker.apis.drf.viewsets import TemplateMakerViewSet +from gcloud.contrib.templatemaker.apis.django import api + +drf_api = DefaultRouter() +drf_api.register(r"template", TemplateMakerViewSet) + +urlpatterns = [ + url(r"^api/maker/", include(drf_api.urls)), + url(r"^api/process_maker/(?P\d+)/", api.maker_template), + url(r"^api/template_detail/(?P\d+)/", api.get_template_market_details), +] From 88d4c522a5a0bd87cfe55ecd5c82bde2ea9bd26a Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Thu, 5 Dec 2024 15:50:00 +0800 Subject: [PATCH 02/14] =?UTF-8?q?fix:=20=E5=BA=94=E7=94=A8=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E4=BF=AE=E6=94=B9=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/default.py | 4 +- config/urls_custom.py | 2 +- env.py | 2 +- .../__init__.py | 0 .../admin.py | 8 +- .../{templatemaker => template_maker}/apps.py | 2 +- .../template_maker/migrations/0001_initial.py | 28 ++++ .../migrations}/__init__.py | 0 .../models.py | 11 +- .../serializers.py} | 28 ++-- .../{templatemaker => template_maker}/urls.py | 11 +- gcloud/contrib/template_maker/viewsets.py | 129 ++++++++++++++++++ .../templatemaker/apis/django/__init__.py | 0 .../contrib/templatemaker/apis/django/api.py | 98 ------------- .../templatemaker/apis/drf/__init__.py | 0 .../apis/drf/viewsets/__init__.py | 14 -- .../apis/drf/viewsets/template_marker.py | 57 -------- .../templatemaker/migrations/0001_initial.py | 37 ----- .../templatemaker/migrations/__init__.py | 0 19 files changed, 189 insertions(+), 242 deletions(-) rename gcloud/contrib/{templatemaker => template_maker}/__init__.py (100%) rename gcloud/contrib/{templatemaker => template_maker}/admin.py (76%) rename gcloud/contrib/{templatemaker => template_maker}/apps.py (75%) create mode 100644 gcloud/contrib/template_maker/migrations/0001_initial.py rename gcloud/contrib/{templatemaker/apis => template_maker/migrations}/__init__.py (100%) rename gcloud/contrib/{templatemaker => template_maker}/models.py (69%) rename gcloud/contrib/{templatemaker/apis/django/validators.py => template_maker/serializers.py} (51%) rename gcloud/contrib/{templatemaker => template_maker}/urls.py (68%) create mode 100644 gcloud/contrib/template_maker/viewsets.py delete mode 100644 gcloud/contrib/templatemaker/apis/django/__init__.py delete mode 100644 gcloud/contrib/templatemaker/apis/django/api.py delete mode 100644 gcloud/contrib/templatemaker/apis/drf/__init__.py delete mode 100644 gcloud/contrib/templatemaker/apis/drf/viewsets/__init__.py delete mode 100644 gcloud/contrib/templatemaker/apis/drf/viewsets/template_marker.py delete mode 100644 gcloud/contrib/templatemaker/migrations/0001_initial.py delete mode 100644 gcloud/contrib/templatemaker/migrations/__init__.py diff --git a/config/default.py b/config/default.py index ad04ebe8dc..d51f86bb6c 100644 --- a/config/default.py +++ b/config/default.py @@ -67,7 +67,7 @@ "gcloud.contrib.develop", "gcloud.contrib.collection", "gcloud.contrib.operate_record", - "gcloud.contrib.templatemaker", + "gcloud.contrib.template_maker", "gcloud.apigw", "gcloud.common_template", "gcloud.label", @@ -887,6 +887,6 @@ def check_engine_admin_permission(request, *args, **kwargs): # 共享模板开关 -ENABLE_FLOW_MARKET = env.ENABLE_FLOW_MARKET +ENABLE_TEMPLATE_MARKET = env.ENABLE_TEMPLATE_MARKET # SRE 商店路由 FLOW_MARKET_API_URL = env.FLOW_MARKET_API_URL diff --git a/config/urls_custom.py b/config/urls_custom.py index 8efd65a41f..05d82a7308 100644 --- a/config/urls_custom.py +++ b/config/urls_custom.py @@ -40,7 +40,7 @@ url(r"^plugin_service/", include("plugin_service.urls")), url(r"^mako_operations/", include("gcloud.mako_template_helper.urls")), url(r"^engine_admin/", include("pipeline.contrib.engine_admin.urls")), - url(r"^template_maker/", include("gcloud.contrib.templatemaker.urls")), + url(r"^template_maker/", include("gcloud.contrib.template_maker.urls")), ] schema_view = get_schema_view( diff --git a/env.py b/env.py index 2cd154eeae..bd4a6d622b 100644 --- a/env.py +++ b/env.py @@ -155,6 +155,6 @@ BK_AUDIT_DATA_TOKEN = os.getenv("BK_AUDIT_DATA_TOKEN", None) # 共享模板开关 -ENABLE_FLOW_MARKET = False if os.getenv("BKAPP_ENABLE_FLOW_MARKET") is None else True +ENABLE_TEMPLATE_MARKET = False if os.getenv("ENABLE_TEMPLATE_MARKET") is None else True # SRE 商店路由 FLOW_MARKET_API_URL = os.getenv("FLOW_MARKET_API_URL", "") diff --git a/gcloud/contrib/templatemaker/__init__.py b/gcloud/contrib/template_maker/__init__.py similarity index 100% rename from gcloud/contrib/templatemaker/__init__.py rename to gcloud/contrib/template_maker/__init__.py diff --git a/gcloud/contrib/templatemaker/admin.py b/gcloud/contrib/template_maker/admin.py similarity index 76% rename from gcloud/contrib/templatemaker/admin.py rename to gcloud/contrib/template_maker/admin.py index d9c3dc45a2..4aebf98ecc 100644 --- a/gcloud/contrib/templatemaker/admin.py +++ b/gcloud/contrib/template_maker/admin.py @@ -13,11 +13,11 @@ from django.contrib import admin -from gcloud.contrib.templatemaker import models +from gcloud.contrib.template_maker import models @admin.register(models.TemplateSharedRecord) class AppMakerAdmin(admin.ModelAdmin): - list_display = ["project_id", "template_id", "template_source", "create", "create_time"] - list_filter = ["project_id", "template_source", "create", "create_time"] - search_fields = ["project_id", "create"] + list_display = ["project_id", "template_id", "creator", "create_at", "extra_info"] + list_filter = ["project_id", "template_id", "creator", "create_at"] + search_fields = ["project_id", "creator"] diff --git a/gcloud/contrib/templatemaker/apps.py b/gcloud/contrib/template_maker/apps.py similarity index 75% rename from gcloud/contrib/templatemaker/apps.py rename to gcloud/contrib/template_maker/apps.py index a5fcdbd722..a05e9b11aa 100644 --- a/gcloud/contrib/templatemaker/apps.py +++ b/gcloud/contrib/template_maker/apps.py @@ -3,4 +3,4 @@ class TemplatemakerConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" - name = "gcloud.contrib.templatemaker" + name = "gcloud.contrib.template_maker" diff --git a/gcloud/contrib/template_maker/migrations/0001_initial.py b/gcloud/contrib/template_maker/migrations/0001_initial.py new file mode 100644 index 0000000000..06b521f4a7 --- /dev/null +++ b/gcloud/contrib/template_maker/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.15 on 2024-12-05 07:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="TemplateSharedRecord", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("project_id", models.IntegerField(default=-1, help_text="项目 ID", verbose_name="项目 ID")), + ("template_id", models.IntegerField(db_index=True, help_text="模版 ID", verbose_name="模版 ID")), + ("creator", models.CharField(help_text="执行者", max_length=128, verbose_name="执行者")), + ("create_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")), + ("extra_info", models.TextField(blank=True, null=True, verbose_name="额外信息")), + ], + options={ + "verbose_name": "模板共享记录 TemplateSharedRecord", + "verbose_name_plural": "模板共享记录 TemplateSharedRecord", + }, + ), + ] diff --git a/gcloud/contrib/templatemaker/apis/__init__.py b/gcloud/contrib/template_maker/migrations/__init__.py similarity index 100% rename from gcloud/contrib/templatemaker/apis/__init__.py rename to gcloud/contrib/template_maker/migrations/__init__.py diff --git a/gcloud/contrib/templatemaker/models.py b/gcloud/contrib/template_maker/models.py similarity index 69% rename from gcloud/contrib/templatemaker/models.py rename to gcloud/contrib/template_maker/models.py index f32dd63fc2..a91e528b3b 100644 --- a/gcloud/contrib/templatemaker/models.py +++ b/gcloud/contrib/template_maker/models.py @@ -15,17 +15,14 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from gcloud.constants import TEMPLATE_SOURCE, PROJECT - class TemplateSharedRecord(models.Model): project_id = models.IntegerField(_("项目 ID"), default=-1, help_text="项目 ID") - template_id = models.IntegerField(_("模版ID"), db_index=True) - template_source = models.CharField(_("流程模板来源"), max_length=32, choices=TEMPLATE_SOURCE, default=PROJECT) - create = models.CharField(_("执行者"), max_length=128, help_text="执行者") - create_time = models.DateTimeField(_("执行时间"), auto_now_add=True, help_text="执行时间") + template_id = models.IntegerField(_("模版 ID"), db_index=True, help_text="模版 ID") + creator = models.CharField(_("执行者"), max_length=128, help_text="执行者") + create_at = models.DateTimeField(_("创建时间"), auto_now_add=True) + extra_info = models.TextField(_("额外信息"), blank=True, null=True) class Meta: verbose_name = _("模板共享记录 TemplateSharedRecord") verbose_name_plural = _("模板共享记录 TemplateSharedRecord") - unique_together = ("project_id", "template_id") diff --git a/gcloud/contrib/templatemaker/apis/django/validators.py b/gcloud/contrib/template_maker/serializers.py similarity index 51% rename from gcloud/contrib/templatemaker/apis/django/validators.py rename to gcloud/contrib/template_maker/serializers.py index af43e70e54..30dafd36fa 100644 --- a/gcloud/contrib/templatemaker/apis/django/validators.py +++ b/gcloud/contrib/template_maker/serializers.py @@ -11,23 +11,23 @@ specific language governing permissions and limitations under the License. """ -import logging -import ujson as json +from rest_framework import serializers +from gcloud.contrib.template_maker.models import TemplateSharedRecord -from django.utils.translation import ugettext_lazy as _ -from gcloud.utils.validate import RequestValidator +class TemplateSharedRecordSerializer(serializers.ModelSerializer): + class Meta: + model = TemplateSharedRecord + fields = "__all__" -logger = logging.getLogger("root") + def validate(self, attrs): + project_id = attrs.get("project_id") + template_id = attrs.get("template_id") + if not project_id or not template_id: + raise serializers.ValidationError("Project ID and Template ID cannot be empty.") -class JsonValidator(RequestValidator): - def validate(self, request, *args, **kwargs): - try: - json.loads(request.body) - except Exception: - message = _("非法请求: 数据错误, 请求不是合法的Json格式 | validate") - logger.error(message) - return False, message + if TemplateSharedRecord.objects.filter(project_id=project_id, template_id=template_id).exists(): + raise serializers.ValidationError("The template has been shared") - return True, "" + return attrs diff --git a/gcloud/contrib/templatemaker/urls.py b/gcloud/contrib/template_maker/urls.py similarity index 68% rename from gcloud/contrib/templatemaker/urls.py rename to gcloud/contrib/template_maker/urls.py index 28bcb0d984..b9aefd91b1 100644 --- a/gcloud/contrib/templatemaker/urls.py +++ b/gcloud/contrib/template_maker/urls.py @@ -13,14 +13,13 @@ from django.conf.urls import include, url from rest_framework.routers import DefaultRouter -from gcloud.contrib.templatemaker.apis.drf.viewsets import TemplateMakerViewSet -from gcloud.contrib.templatemaker.apis.django import api +from gcloud.contrib.template_maker.viewsets import TemplateMarketViewSet, SharedTemplateViewSet + drf_api = DefaultRouter() -drf_api.register(r"template", TemplateMakerViewSet) +drf_api.register(r"template", TemplateMarketViewSet) +drf_api.register(r"shared", SharedTemplateViewSet) urlpatterns = [ - url(r"^api/maker/", include(drf_api.urls)), - url(r"^api/process_maker/(?P\d+)/", api.maker_template), - url(r"^api/template_detail/(?P\d+)/", api.get_template_market_details), + url(r"^api/", include(drf_api.urls)), ] diff --git a/gcloud/contrib/template_maker/viewsets.py b/gcloud/contrib/template_maker/viewsets.py new file mode 100644 index 0000000000..f3fd002e15 --- /dev/null +++ b/gcloud/contrib/template_maker/viewsets.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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. +""" + +import logging +import requests + +from rest_framework import viewsets +from rest_framework.response import Response +from rest_framework import permissions + +from gcloud.conf import settings +from gcloud import err_code +from gcloud.contrib.template_maker.serializers import TemplateSharedRecordSerializer +from gcloud.contrib.template_maker.models import TemplateSharedRecord +from gcloud.core.apis.drf.serilaziers.task_template import TaskTemplateSerializer, TaskTemplateListSerializer +from gcloud.taskflow3.models import TaskTemplate +from gcloud.core.apis.drf.viewsets.base import GcloudModelViewSet + + +class TemplateMarkerPermission(permissions.BasePermission): + def has_permission(self, request, view): + template_id = view.kwargs.get("pk") + project_id = request.GET.get("project_id") + + if not template_id or not project_id: + logging.warning("template_id is required.") + return False + try: + TemplateSharedRecord.objects.get(template_id=template_id, project_id=project_id) + except Exception: + logging.warning("template_id {} does not exist.".format(template_id)) + return False + return True + + +class SharedTemplatePermission(permissions.BasePermission): + def has_permission(self, request, view): + if not settings.ENABLE_TEMPLATE_MARKET: + return False + return True + + +class TemplateMarketViewSet(GcloudModelViewSet): + queryset = TaskTemplate.objects.filter(pipeline_template__isnull=False, is_deleted=False) + permission_classes = [permissions.IsAuthenticated, TemplateMarkerPermission] + + def get_serializer_class(self): + if self.action == "list": + return TaskTemplateListSerializer + return TaskTemplateSerializer + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(instance) + + return Response(serializer.data) + + +class SharedTemplateViewSet(viewsets.ModelViewSet): + queryset = TemplateSharedRecord.objects.all() + serializer_class = TemplateSharedRecordSerializer + permission_classes = [permissions.IsAuthenticated, SharedTemplatePermission] + + def _get_market_routing(self, market_url): + return f"{settings.FLOW_MARKET_API_URL}/{market_url}" + + def retrieve(self, request, *args, **kwargs): + project_id = kwargs.get("pk") + template_id = request.GET.get("template_id") + + url = self._get_market_routing("market/details/") + data = {"project_id": project_id, "template_id": template_id} + + # 根据业务id和模板id从第三方接口获取模板详情 + result = requests.post(url, data=data) + + if not result or result.status_code != 200: + logging.exception("Get market template details from third party fails") + return Response( + { + "result": False, + "message": "Get market template details from third party fails", + "code": err_code.OPERATION_FAIL.code, + } + ) + + return Response(result.json()) + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + project_id = serializer.validated_data.get("project_id") + template_id = serializer.validated_data.get("template_id") + + try: + TaskTemplate.objects.get(project_id=project_id, id=template_id) + except TaskTemplate.DoesNotExist: + logging.warning(f"Template with project_id {project_id} and template_id {template_id} not found.") + return Response({"result": False, "message": "Template not found", "code": err_code.OPERATION_FAIL.code}) + + # 执行第三方接口调用 + url = self._get_market_routing("prod/api/") + data = {} + headers = None + result = requests.post(url, headers=headers, data=data) + if not result: + logging.exception("Sharing template to SRE store fails") + return Response( + { + "result": False, + "message": "Sharing template to SRE store fails", + "code": err_code.OPERATION_FAIL.code, + }, + ) + + self.perform_create(serializer) + + return Response({"result": True, "message": "Share template successfully", "code": err_code.SUCCESS.code}) diff --git a/gcloud/contrib/templatemaker/apis/django/__init__.py b/gcloud/contrib/templatemaker/apis/django/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gcloud/contrib/templatemaker/apis/django/api.py b/gcloud/contrib/templatemaker/apis/django/api.py deleted file mode 100644 index 0f6f687d6a..0000000000 --- a/gcloud/contrib/templatemaker/apis/django/api.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community -Edition) available. -Copyright (C) 2017 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. -""" -import json -import requests -import logging - -from rest_framework.response import Response -from django.views.decorators.http import require_POST, require_GET -from django.http import JsonResponse - -from gcloud.conf import settings -from gcloud import err_code -from gcloud.contrib.templatemaker.models import TemplateSharedRecord -from gcloud.tasktmpl3.models import TaskTemplate -from gcloud.utils.decorators import request_validate -from gcloud.contrib.templatemaker.apis.django.validators import JsonValidator - - -def _get_market_routing(market_url): - return "{}/{}".format(settings.FLOW_MARKET_API_URL, market_url) - - -@require_GET -def get_template_market_details(request, template_id): - project_id = json.loads(request.body).get("project_id") - - url = _get_market_routing("market/details/") - - kwargs = { - "project_id": project_id, - } - - # 根据业务id和模板id从第三方接口获取模板详情 - result = requests.post(url, data=kwargs) - - if not result: - logging.exception("get market template details from third party fails") - return Response( - { - "result": False, - "message": "get market template details from third party fails", - "code": err_code.OPERATION_FAIL.code, - } - ) - - return True - - -@require_POST -@request_validate(JsonValidator) -def maker_template(request, template_id): - if not settings.ENABLE_FLOW_MARKET: - return False - - params = json.loads(request.body) - project_id = params.get("project_id") - try: - TaskTemplate.objects.filter(id=template_id, project__id=project_id).first() - except Exception as e: - logging.exception(e) - return Response( - {"result": False, "message": "template_id does not exist", "code": err_code.OPERATION_FAIL.code} - ) - - url = _get_market_routing("prod/api/") - - kwargs = None - - headers = None - - # 调用第三方接口 - result = requests.post(url, headers=headers, data=kwargs) - - if not result: - logging.exception("Sharing template to SRE store fails") - return JsonResponse( - {"result": False, "message": "Sharing template to SRE store fails", "code": err_code.OPERATION_FAIL.code} - ) - - record, created = TemplateSharedRecord.objects.get_or_create( - project_id=params["project_id"], - template_id=template_id, - defaults={"template_source": params["template_source"], "create": request.user.username}, - ) - if not created: - return JsonResponse({"result": False, "message": "Record already exists", "code": err_code.OPERATION_FAIL.code}) - - return JsonResponse({"result": True, "message": "shared success", "code": err_code.SUCCESS.code}) diff --git a/gcloud/contrib/templatemaker/apis/drf/__init__.py b/gcloud/contrib/templatemaker/apis/drf/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gcloud/contrib/templatemaker/apis/drf/viewsets/__init__.py b/gcloud/contrib/templatemaker/apis/drf/viewsets/__init__.py deleted file mode 100644 index ea4177dd98..0000000000 --- a/gcloud/contrib/templatemaker/apis/drf/viewsets/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community -Edition) available. -Copyright (C) 2017 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 gcloud.contrib.templatemaker.apis.drf.viewsets.template_marker import TemplateMakerViewSet # noqa diff --git a/gcloud/contrib/templatemaker/apis/drf/viewsets/template_marker.py b/gcloud/contrib/templatemaker/apis/drf/viewsets/template_marker.py deleted file mode 100644 index 6570b03130..0000000000 --- a/gcloud/contrib/templatemaker/apis/drf/viewsets/template_marker.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community -Edition) available. -Copyright (C) 2017 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. -""" - -import logging - -from rest_framework.response import Response -from rest_framework import permissions - -from gcloud.contrib.templatemaker.models import TemplateSharedRecord -from gcloud.core.apis.drf.serilaziers.task_template import TaskTemplateSerializer, TaskTemplateListSerializer -from gcloud.taskflow3.models import TaskTemplate -from gcloud.label.models import TemplateLabelRelation -from gcloud.core.apis.drf.viewsets.base import GcloudModelViewSet - - -class HasValidTemplateID(permissions.BasePermission): - def has_permission(self, request, view): - template_id = view.kwargs.get("pk") - - if not template_id: - logging.warning("template_id is required.") - return False - try: - TemplateSharedRecord.objects.get(template_id=template_id) - except Exception: - logging.warning("template_id {} does not exist.".format(template_id)) - return False - return True - - -class TemplateMakerViewSet(GcloudModelViewSet): - queryset = TaskTemplate.objects.filter(pipeline_template__isnull=False, is_deleted=False) - permission_classes = [permissions.IsAuthenticated, HasValidTemplateID] - - def get_serializer_class(self): - if self.action == "list": - return TaskTemplateListSerializer - return TaskTemplateSerializer - - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - serializer = self.get_serializer(instance) - data = serializer.data - labels = TemplateLabelRelation.objects.fetch_templates_labels([instance.id]).get(instance.id, []) - data["template_labels"] = [label["label_id"] for label in labels] - - return Response(data) diff --git a/gcloud/contrib/templatemaker/migrations/0001_initial.py b/gcloud/contrib/templatemaker/migrations/0001_initial.py deleted file mode 100644 index 9643d92d63..0000000000 --- a/gcloud/contrib/templatemaker/migrations/0001_initial.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 3.2.15 on 2024-12-04 10:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [] - - operations = [ - migrations.CreateModel( - name="TemplateSharedRecord", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("project_id", models.IntegerField(default=-1, help_text="项目 ID", verbose_name="项目 ID")), - ("template_id", models.IntegerField(db_index=True, verbose_name="模版ID")), - ( - "template_source", - models.CharField( - choices=[("project", "项目流程"), ("common", "公共流程"), ("onetime", "一次性任务")], - default="project", - max_length=32, - verbose_name="流程模板来源", - ), - ), - ("create", models.CharField(help_text="执行者", max_length=128, verbose_name="执行者")), - ("create_time", models.DateTimeField(auto_now_add=True, help_text="执行时间", verbose_name="执行时间")), - ], - options={ - "verbose_name": "模板共享记录 TemplateSharedRecord", - "verbose_name_plural": "模板共享记录 TemplateSharedRecord", - "unique_together": {("project_id", "template_id")}, - }, - ), - ] diff --git a/gcloud/contrib/templatemaker/migrations/__init__.py b/gcloud/contrib/templatemaker/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From 39204d06e0e492a44e2749319596d3178539d798 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Fri, 6 Dec 2024 15:29:36 +0800 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20=E6=B5=81=E7=A8=8B=E8=B7=A8?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E5=A4=8D=E5=88=B6=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/default.py | 10 ++- config/urls_custom.py | 2 +- env.py | 8 +- gcloud/apigw/urls.py | 2 + .../apigw/views/copy_template_across_biz.py | 78 +++++++++++++++++++ .../__init__.py | 0 .../admin.py | 2 +- .../apps.py | 2 +- .../migrations/0001_initial.py | 6 +- .../migrations/__init__.py | 0 .../models.py | 4 +- .../serializers.py | 24 +++--- .../urls.py | 6 +- .../viewsets.py | 72 ++++++----------- 14 files changed, 134 insertions(+), 82 deletions(-) create mode 100644 gcloud/apigw/views/copy_template_across_biz.py rename gcloud/contrib/{template_maker => template_market}/__init__.py (100%) rename gcloud/contrib/{template_maker => template_market}/admin.py (95%) rename gcloud/contrib/{template_maker => template_market}/apps.py (74%) rename gcloud/contrib/{template_maker => template_market}/migrations/0001_initial.py (80%) rename gcloud/contrib/{template_maker => template_market}/migrations/__init__.py (100%) rename gcloud/contrib/{template_maker => template_market}/models.py (89%) rename gcloud/contrib/{template_maker => template_market}/serializers.py (58%) rename gcloud/contrib/{template_maker => template_market}/urls.py (79%) rename gcloud/contrib/{template_maker => template_market}/viewsets.py (56%) diff --git a/config/default.py b/config/default.py index d51f86bb6c..245d00d588 100644 --- a/config/default.py +++ b/config/default.py @@ -67,7 +67,7 @@ "gcloud.contrib.develop", "gcloud.contrib.collection", "gcloud.contrib.operate_record", - "gcloud.contrib.template_maker", + "gcloud.contrib.template_market", "gcloud.apigw", "gcloud.common_template", "gcloud.label", @@ -886,7 +886,9 @@ def check_engine_admin_permission(request, *args, **kwargs): print(f"BROKER_URL: {BROKER_URL}") -# 共享模板开关 +# 流程商店 ENABLE_TEMPLATE_MARKET = env.ENABLE_TEMPLATE_MARKET -# SRE 商店路由 -FLOW_MARKET_API_URL = env.FLOW_MARKET_API_URL +# 流程商店 API 地址 +TEMPLATE_MARKET_API_URL = env.TEMPLATE_MARKET_API_URL +# 流程跨业务复制 +ENABLE_APIGW_COPY_TEMPLATE = env.ENABLE_APIGW_COPY_TEMPLATE diff --git a/config/urls_custom.py b/config/urls_custom.py index 05d82a7308..8c19756ae8 100644 --- a/config/urls_custom.py +++ b/config/urls_custom.py @@ -40,7 +40,7 @@ url(r"^plugin_service/", include("plugin_service.urls")), url(r"^mako_operations/", include("gcloud.mako_template_helper.urls")), url(r"^engine_admin/", include("pipeline.contrib.engine_admin.urls")), - url(r"^template_maker/", include("gcloud.contrib.template_maker.urls")), + url(r"^template_market/", include("gcloud.contrib.template_market.urls")), ] schema_view = get_schema_view( diff --git a/env.py b/env.py index bd4a6d622b..472cc68e5b 100644 --- a/env.py +++ b/env.py @@ -154,7 +154,9 @@ BK_AUDIT_ENDPOINT = os.getenv("BK_AUDIT_ENDPOINT", None) BK_AUDIT_DATA_TOKEN = os.getenv("BK_AUDIT_DATA_TOKEN", None) -# 共享模板开关 +# 流程商店 ENABLE_TEMPLATE_MARKET = False if os.getenv("ENABLE_TEMPLATE_MARKET") is None else True -# SRE 商店路由 -FLOW_MARKET_API_URL = os.getenv("FLOW_MARKET_API_URL", "") +# 流程商店 API 地址 +TEMPLATE_MARKET_API_URL = os.getenv("TEMPLATE_MARKET_API_URL", "") +# 流程跨业务复制 +ENABLE_APIGW_COPY_TEMPLATE = False if os.getenv("ENABLE_APIGW_COPY_TEMPLATE") is None else True diff --git a/gcloud/apigw/urls.py b/gcloud/apigw/urls.py index a4f4751328..0806b5d959 100644 --- a/gcloud/apigw/urls.py +++ b/gcloud/apigw/urls.py @@ -55,6 +55,7 @@ from gcloud.apigw.views.register_project import register_project from gcloud.apigw.views.set_periodic_task_enabled import set_periodic_task_enabled from gcloud.apigw.views.start_task import start_task +from gcloud.apigw.views.copy_template_across_biz import copy_template_across_biz urlpatterns = [ url(r"^dispatch_plugin_query/$", dispatch_plugin_query), @@ -129,4 +130,5 @@ url(r"^create_clocked_task/(?P\d+)/(?P\d+)/$", create_clocked_task), url(r"^get_mini_app_list/(?P\d+)/$", get_mini_app_list), url(r"^get_task_count/(?P\d+)/$", get_task_count), + url(r"^copy_template_across_biz/(?P\d+)/$", copy_template_across_biz), ] diff --git a/gcloud/apigw/views/copy_template_across_biz.py b/gcloud/apigw/views/copy_template_across_biz.py new file mode 100644 index 0000000000..663aa5ecdf --- /dev/null +++ b/gcloud/apigw/views/copy_template_across_biz.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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. +""" + +import ujson as json +from apigw_manager.apigw.decorators import apigw_require +from blueapps.account.decorators import login_exempt +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_POST + +from gcloud import err_code +from gcloud.apigw.decorators import ( + mark_request_whether_is_trust, + project_inject, + return_json_response, +) + +from gcloud.conf import settings +from gcloud.apigw.views.utils import logger +from gcloud.tasktmpl3.models import TaskTemplate +from gcloud.template_base.utils import format_import_result_to_response_data + + +@login_exempt +@csrf_exempt +@require_POST +@apigw_require +@return_json_response +@project_inject +@mark_request_whether_is_trust +def copy_template_across_biz(request, project_id): + if not request.is_trust or not settings.ENABLE_APIGW_COPY_TEMPLATE: + return { + "result": False, + "message": "you have no permission to call this api.", + "code": err_code.REQUEST_FORBIDDEN_INVALID.code, + } + + params_data = json.loads(request.body) + new_project_id = params_data.get("new_project_id") + template_id = params_data.get("template_id") + template_id_list = [template_id] if template_id is not None else [] + + if not new_project_id or not template_id_list: + return { + "result": False, + "message": "missing or invalid parameter", + "code": err_code.REQUEST_PARAM_INVALID.code, + } + + try: + export_data = TaskTemplate.objects.export_templates( + template_id_list, is_full=False, project_id=request.project.id + ) + import_result = TaskTemplate.objects.import_templates( + template_data=export_data, + override=False, + project_id=new_project_id, + operator=request.user.username, + ) + except Exception as e: + logger.exception("[API] copy common tempalte error: {}".format(e)) + return { + "result": False, + "message": "invalid flow data or error occur, please contact administrator", + "code": err_code.UNKNOWN_ERROR.code, + } + + return format_import_result_to_response_data(import_result) diff --git a/gcloud/contrib/template_maker/__init__.py b/gcloud/contrib/template_market/__init__.py similarity index 100% rename from gcloud/contrib/template_maker/__init__.py rename to gcloud/contrib/template_market/__init__.py diff --git a/gcloud/contrib/template_maker/admin.py b/gcloud/contrib/template_market/admin.py similarity index 95% rename from gcloud/contrib/template_maker/admin.py rename to gcloud/contrib/template_market/admin.py index 4aebf98ecc..37f2356572 100644 --- a/gcloud/contrib/template_maker/admin.py +++ b/gcloud/contrib/template_market/admin.py @@ -13,7 +13,7 @@ from django.contrib import admin -from gcloud.contrib.template_maker import models +from gcloud.contrib.template_market import models @admin.register(models.TemplateSharedRecord) diff --git a/gcloud/contrib/template_maker/apps.py b/gcloud/contrib/template_market/apps.py similarity index 74% rename from gcloud/contrib/template_maker/apps.py rename to gcloud/contrib/template_market/apps.py index a05e9b11aa..891af520fc 100644 --- a/gcloud/contrib/template_maker/apps.py +++ b/gcloud/contrib/template_market/apps.py @@ -3,4 +3,4 @@ class TemplatemakerConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" - name = "gcloud.contrib.template_maker" + name = "gcloud.contrib.template_market" diff --git a/gcloud/contrib/template_maker/migrations/0001_initial.py b/gcloud/contrib/template_market/migrations/0001_initial.py similarity index 80% rename from gcloud/contrib/template_maker/migrations/0001_initial.py rename to gcloud/contrib/template_market/migrations/0001_initial.py index 06b521f4a7..d1cc1e26e8 100644 --- a/gcloud/contrib/template_maker/migrations/0001_initial.py +++ b/gcloud/contrib/template_market/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.15 on 2024-12-05 07:07 +# Generated by Django 3.2.15 on 2024-12-06 06:47 from django.db import migrations, models @@ -16,9 +16,9 @@ class Migration(migrations.Migration): ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ("project_id", models.IntegerField(default=-1, help_text="项目 ID", verbose_name="项目 ID")), ("template_id", models.IntegerField(db_index=True, help_text="模版 ID", verbose_name="模版 ID")), - ("creator", models.CharField(help_text="执行者", max_length=128, verbose_name="执行者")), + ("creator", models.CharField(default="", max_length=32, verbose_name="创建者")), ("create_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")), - ("extra_info", models.TextField(blank=True, null=True, verbose_name="额外信息")), + ("extra_info", models.JSONField(blank=True, null=True, verbose_name="额外信息")), ], options={ "verbose_name": "模板共享记录 TemplateSharedRecord", diff --git a/gcloud/contrib/template_maker/migrations/__init__.py b/gcloud/contrib/template_market/migrations/__init__.py similarity index 100% rename from gcloud/contrib/template_maker/migrations/__init__.py rename to gcloud/contrib/template_market/migrations/__init__.py diff --git a/gcloud/contrib/template_maker/models.py b/gcloud/contrib/template_market/models.py similarity index 89% rename from gcloud/contrib/template_maker/models.py rename to gcloud/contrib/template_market/models.py index a91e528b3b..39713bd8fc 100644 --- a/gcloud/contrib/template_maker/models.py +++ b/gcloud/contrib/template_market/models.py @@ -19,9 +19,9 @@ class TemplateSharedRecord(models.Model): project_id = models.IntegerField(_("项目 ID"), default=-1, help_text="项目 ID") template_id = models.IntegerField(_("模版 ID"), db_index=True, help_text="模版 ID") - creator = models.CharField(_("执行者"), max_length=128, help_text="执行者") + creator = models.CharField(_("创建者"), max_length=32, default="") create_at = models.DateTimeField(_("创建时间"), auto_now_add=True) - extra_info = models.TextField(_("额外信息"), blank=True, null=True) + extra_info = models.JSONField(_("额外信息"), blank=True, null=True) class Meta: verbose_name = _("模板共享记录 TemplateSharedRecord") diff --git a/gcloud/contrib/template_maker/serializers.py b/gcloud/contrib/template_market/serializers.py similarity index 58% rename from gcloud/contrib/template_maker/serializers.py rename to gcloud/contrib/template_market/serializers.py index 30dafd36fa..738fa6e806 100644 --- a/gcloud/contrib/template_maker/serializers.py +++ b/gcloud/contrib/template_market/serializers.py @@ -12,22 +12,18 @@ """ from rest_framework import serializers -from gcloud.contrib.template_maker.models import TemplateSharedRecord + +from gcloud.constants import DATETIME_FORMAT +from gcloud.contrib.template_market.models import TemplateSharedRecord class TemplateSharedRecordSerializer(serializers.ModelSerializer): + template_id = serializers.CharField(required=True, help_text="模板id") + project_id = serializers.CharField(required=True, help_text="项目id") + creator = serializers.CharField(required=False, max_length=32, help_text="创建者") + create_at = serializers.DateTimeField(required=False, help_text="创建时间", format=DATETIME_FORMAT) + extra_info = serializers.JSONField(required=False, allow_null=True, help_text="额外信息") + class Meta: model = TemplateSharedRecord - fields = "__all__" - - def validate(self, attrs): - project_id = attrs.get("project_id") - template_id = attrs.get("template_id") - - if not project_id or not template_id: - raise serializers.ValidationError("Project ID and Template ID cannot be empty.") - - if TemplateSharedRecord.objects.filter(project_id=project_id, template_id=template_id).exists(): - raise serializers.ValidationError("The template has been shared") - - return attrs + fields = ["project_id", "template_id", "creator", "create_at", "extra_info"] diff --git a/gcloud/contrib/template_maker/urls.py b/gcloud/contrib/template_market/urls.py similarity index 79% rename from gcloud/contrib/template_maker/urls.py rename to gcloud/contrib/template_market/urls.py index b9aefd91b1..5a3a866805 100644 --- a/gcloud/contrib/template_maker/urls.py +++ b/gcloud/contrib/template_market/urls.py @@ -13,12 +13,12 @@ from django.conf.urls import include, url from rest_framework.routers import DefaultRouter -from gcloud.contrib.template_maker.viewsets import TemplateMarketViewSet, SharedTemplateViewSet +from gcloud.contrib.template_market.viewsets import StoreTemplateViewSet, SharedProcessTemplateViewSet drf_api = DefaultRouter() -drf_api.register(r"template", TemplateMarketViewSet) -drf_api.register(r"shared", SharedTemplateViewSet) +drf_api.register(r"store_templates", StoreTemplateViewSet) +drf_api.register(r"shared_process_templates", SharedProcessTemplateViewSet) urlpatterns = [ url(r"^api/", include(drf_api.urls)), diff --git a/gcloud/contrib/template_maker/viewsets.py b/gcloud/contrib/template_market/viewsets.py similarity index 56% rename from gcloud/contrib/template_maker/viewsets.py rename to gcloud/contrib/template_market/viewsets.py index f3fd002e15..f6c7acbafc 100644 --- a/gcloud/contrib/template_maker/viewsets.py +++ b/gcloud/contrib/template_market/viewsets.py @@ -12,7 +12,6 @@ """ import logging -import requests from rest_framework import viewsets from rest_framework.response import Response @@ -20,20 +19,19 @@ from gcloud.conf import settings from gcloud import err_code -from gcloud.contrib.template_maker.serializers import TemplateSharedRecordSerializer -from gcloud.contrib.template_maker.models import TemplateSharedRecord -from gcloud.core.apis.drf.serilaziers.task_template import TaskTemplateSerializer, TaskTemplateListSerializer +from gcloud.contrib.template_market.serializers import TemplateSharedRecordSerializer +from gcloud.contrib.template_market.models import TemplateSharedRecord +from gcloud.core.apis.drf.serilaziers.task_template import TaskTemplateSerializer from gcloud.taskflow3.models import TaskTemplate -from gcloud.core.apis.drf.viewsets.base import GcloudModelViewSet -class TemplateMarkerPermission(permissions.BasePermission): +class StoreTemplatePermission(permissions.BasePermission): def has_permission(self, request, view): template_id = view.kwargs.get("pk") project_id = request.GET.get("project_id") if not template_id or not project_id: - logging.warning("template_id is required.") + logging.warning("Missing required parameters.") return False try: TemplateSharedRecord.objects.get(template_id=template_id, project_id=project_id) @@ -43,58 +41,45 @@ def has_permission(self, request, view): return True -class SharedTemplatePermission(permissions.BasePermission): +class SharedProcessTemplatePermission(permissions.BasePermission): def has_permission(self, request, view): if not settings.ENABLE_TEMPLATE_MARKET: return False return True -class TemplateMarketViewSet(GcloudModelViewSet): +class StoreTemplateViewSet(viewsets.ViewSet): queryset = TaskTemplate.objects.filter(pipeline_template__isnull=False, is_deleted=False) - permission_classes = [permissions.IsAuthenticated, TemplateMarkerPermission] - - def get_serializer_class(self): - if self.action == "list": - return TaskTemplateListSerializer - return TaskTemplateSerializer + serializer_class = TaskTemplateSerializer + permission_classes = [permissions.IsAuthenticated, StoreTemplatePermission] def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - serializer = self.get_serializer(instance) + instance = self.queryset.get(id=kwargs["pk"], project_id=request.GET.get("project_id")) + serializer = self.serializer_class(instance) return Response(serializer.data) -class SharedTemplateViewSet(viewsets.ModelViewSet): +class SharedProcessTemplateViewSet(viewsets.ModelViewSet): queryset = TemplateSharedRecord.objects.all() serializer_class = TemplateSharedRecordSerializer - permission_classes = [permissions.IsAuthenticated, SharedTemplatePermission] + permission_classes = [permissions.IsAuthenticated, SharedProcessTemplatePermission] def _get_market_routing(self, market_url): - return f"{settings.FLOW_MARKET_API_URL}/{market_url}" + return f"{settings.TEMPLATE_MARKET_API_URL}/{market_url}" def retrieve(self, request, *args, **kwargs): - project_id = kwargs.get("pk") - template_id = request.GET.get("template_id") + serializer = self.get_serializer(data=request.GET) + serializer.is_valid(raise_exception=True) - url = self._get_market_routing("market/details/") - data = {"project_id": project_id, "template_id": template_id} + project_id = serializer.validated_data.get("project_id") + template_id = serializer.validated_data.get("template_id") - # 根据业务id和模板id从第三方接口获取模板详情 - result = requests.post(url, data=data) + data = {"project_id": project_id, "template_id": template_id} - if not result or result.status_code != 200: - logging.exception("Get market template details from third party fails") - return Response( - { - "result": False, - "message": "Get market template details from third party fails", - "code": err_code.OPERATION_FAIL.code, - } - ) + # todo: 调用第三方接口查询信息 - return Response(result.json()) + return Response({"result": True, "data": data, "code": err_code.SUCCESS.code}) def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) @@ -109,20 +94,7 @@ def create(self, request, *args, **kwargs): logging.warning(f"Template with project_id {project_id} and template_id {template_id} not found.") return Response({"result": False, "message": "Template not found", "code": err_code.OPERATION_FAIL.code}) - # 执行第三方接口调用 - url = self._get_market_routing("prod/api/") - data = {} - headers = None - result = requests.post(url, headers=headers, data=data) - if not result: - logging.exception("Sharing template to SRE store fails") - return Response( - { - "result": False, - "message": "Sharing template to SRE store fails", - "code": err_code.OPERATION_FAIL.code, - }, - ) + # todo: 调用第三方接口实现共享 self.perform_create(serializer) From cbb17a8757ef6711e794c9ee0ce14c2104cbea48 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Tue, 10 Dec 2024 08:46:00 +0800 Subject: [PATCH 04/14] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E9=97=AE=E9=A2=98=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/default.py | 2 - env.py | 2 - .../commands/data/api-resources.yml | 24 +++ gcloud/apigw/urls.py | 4 +- .../copy_template_across_project.py | 28 ++++ ...biz.py => copy_template_across_project.py} | 26 ++- gcloud/contrib/template_market/admin.py | 2 +- .../migrations/0001_initial.py | 8 +- gcloud/contrib/template_market/models.py | 18 ++- gcloud/contrib/template_market/permission.py | 40 +++++ gcloud/contrib/template_market/serializers.py | 46 +++++- gcloud/contrib/template_market/urls.py | 10 +- gcloud/contrib/template_market/viewsets.py | 152 +++++++++++------- 13 files changed, 269 insertions(+), 93 deletions(-) create mode 100644 gcloud/apigw/validators/copy_template_across_project.py rename gcloud/apigw/views/{copy_template_across_biz.py => copy_template_across_project.py} (73%) create mode 100644 gcloud/contrib/template_market/permission.py diff --git a/config/default.py b/config/default.py index 245d00d588..be7bbd0adb 100644 --- a/config/default.py +++ b/config/default.py @@ -890,5 +890,3 @@ def check_engine_admin_permission(request, *args, **kwargs): ENABLE_TEMPLATE_MARKET = env.ENABLE_TEMPLATE_MARKET # 流程商店 API 地址 TEMPLATE_MARKET_API_URL = env.TEMPLATE_MARKET_API_URL -# 流程跨业务复制 -ENABLE_APIGW_COPY_TEMPLATE = env.ENABLE_APIGW_COPY_TEMPLATE diff --git a/env.py b/env.py index 472cc68e5b..2a4e1546ed 100644 --- a/env.py +++ b/env.py @@ -158,5 +158,3 @@ ENABLE_TEMPLATE_MARKET = False if os.getenv("ENABLE_TEMPLATE_MARKET") is None else True # 流程商店 API 地址 TEMPLATE_MARKET_API_URL = os.getenv("TEMPLATE_MARKET_API_URL", "") -# 流程跨业务复制 -ENABLE_APIGW_COPY_TEMPLATE = False if os.getenv("ENABLE_APIGW_COPY_TEMPLATE") is None else True diff --git a/gcloud/apigw/management/commands/data/api-resources.yml b/gcloud/apigw/management/commands/data/api-resources.yml index de54d6e544..d76ef16d2c 100644 --- a/gcloud/apigw/management/commands/data/api-resources.yml +++ b/gcloud/apigw/management/commands/data/api-resources.yml @@ -757,3 +757,27 @@ paths: authConfig: userVerifiedRequired: true disabledStages: [] + /copy_template_across_project/{project_id}/: + post: + operationId: copy_template_across_project + description: 跨业务复制模板 + tags: + - 通用接口 + responses: + default: + description: '' + x-bk-apigateway-resource: + isPublic: true + allowApplyPermission: true + matchSubpath: false + backend: + type: HTTP + method: post + path: /{env.api_sub_path}apigw/copy_template_across_project/{project_id>}/ + matchSubpath: false + timeout: 0 + upstreams: {} + transformHeaders: {} + authConfig: + userVerifiedRequired: true + disabledStages: [] \ No newline at end of file diff --git a/gcloud/apigw/urls.py b/gcloud/apigw/urls.py index 0806b5d959..51a09e31b4 100644 --- a/gcloud/apigw/urls.py +++ b/gcloud/apigw/urls.py @@ -55,7 +55,7 @@ from gcloud.apigw.views.register_project import register_project from gcloud.apigw.views.set_periodic_task_enabled import set_periodic_task_enabled from gcloud.apigw.views.start_task import start_task -from gcloud.apigw.views.copy_template_across_biz import copy_template_across_biz +from gcloud.apigw.views.copy_template_across_project import copy_template_across_project urlpatterns = [ url(r"^dispatch_plugin_query/$", dispatch_plugin_query), @@ -130,5 +130,5 @@ url(r"^create_clocked_task/(?P\d+)/(?P\d+)/$", create_clocked_task), url(r"^get_mini_app_list/(?P\d+)/$", get_mini_app_list), url(r"^get_task_count/(?P\d+)/$", get_task_count), - url(r"^copy_template_across_biz/(?P\d+)/$", copy_template_across_biz), + url(r"^copy_template_across_project/(?P\d+)/$", copy_template_across_project), ] diff --git a/gcloud/apigw/validators/copy_template_across_project.py b/gcloud/apigw/validators/copy_template_across_project.py new file mode 100644 index 0000000000..27eaa1cc1f --- /dev/null +++ b/gcloud/apigw/validators/copy_template_across_project.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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. +""" +import json + +from gcloud.utils.validate import ObjectJsonBodyValidator + + +class CopyTemplateAcrossProjectValidator(ObjectJsonBodyValidator): + def validate(self, request, *args, **kwargs): + valid, err = super().validate(request, *args, **kwargs) + + if not valid: + return valid, err + + if not json.loads(request.body).get("new_project_id") or not json.loads(request.body).get("template_id"): + return False, "new_project_id and template_id is required" + + return True, "" diff --git a/gcloud/apigw/views/copy_template_across_biz.py b/gcloud/apigw/views/copy_template_across_project.py similarity index 73% rename from gcloud/apigw/views/copy_template_across_biz.py rename to gcloud/apigw/views/copy_template_across_project.py index 663aa5ecdf..d7673992cc 100644 --- a/gcloud/apigw/views/copy_template_across_biz.py +++ b/gcloud/apigw/views/copy_template_across_project.py @@ -24,10 +24,11 @@ return_json_response, ) -from gcloud.conf import settings from gcloud.apigw.views.utils import logger from gcloud.tasktmpl3.models import TaskTemplate from gcloud.template_base.utils import format_import_result_to_response_data +from gcloud.utils.decorators import request_validate +from gcloud.apigw.validators.copy_template_across_project import CopyTemplateAcrossProjectValidator @login_exempt @@ -36,9 +37,10 @@ @apigw_require @return_json_response @project_inject +@request_validate(CopyTemplateAcrossProjectValidator) @mark_request_whether_is_trust -def copy_template_across_biz(request, project_id): - if not request.is_trust or not settings.ENABLE_APIGW_COPY_TEMPLATE: +def copy_template_across_project(request, project_id): + if not request.is_trust: return { "result": False, "message": "you have no permission to call this api.", @@ -46,21 +48,11 @@ def copy_template_across_biz(request, project_id): } params_data = json.loads(request.body) - new_project_id = params_data.get("new_project_id") - template_id = params_data.get("template_id") - template_id_list = [template_id] if template_id is not None else [] - - if not new_project_id or not template_id_list: - return { - "result": False, - "message": "missing or invalid parameter", - "code": err_code.REQUEST_PARAM_INVALID.code, - } + new_project_id = params_data["new_project_id"] + template_id = params_data["template_id"] try: - export_data = TaskTemplate.objects.export_templates( - template_id_list, is_full=False, project_id=request.project.id - ) + export_data = TaskTemplate.objects.export_templates([template_id], is_full=False, project_id=request.project.id) import_result = TaskTemplate.objects.import_templates( template_data=export_data, override=False, @@ -68,7 +60,7 @@ def copy_template_across_biz(request, project_id): operator=request.user.username, ) except Exception as e: - logger.exception("[API] copy common tempalte error: {}".format(e)) + logger.exception("The process fails to be replicated across services: {}".format(e)) return { "result": False, "message": "invalid flow data or error occur, please contact administrator", diff --git a/gcloud/contrib/template_market/admin.py b/gcloud/contrib/template_market/admin.py index 37f2356572..b2d3c729ab 100644 --- a/gcloud/contrib/template_market/admin.py +++ b/gcloud/contrib/template_market/admin.py @@ -17,7 +17,7 @@ @admin.register(models.TemplateSharedRecord) -class AppMakerAdmin(admin.ModelAdmin): +class TemplateMarketAdmin(admin.ModelAdmin): list_display = ["project_id", "template_id", "creator", "create_at", "extra_info"] list_filter = ["project_id", "template_id", "creator", "create_at"] search_fields = ["project_id", "creator"] diff --git a/gcloud/contrib/template_market/migrations/0001_initial.py b/gcloud/contrib/template_market/migrations/0001_initial.py index d1cc1e26e8..c9e5d1f27d 100644 --- a/gcloud/contrib/template_market/migrations/0001_initial.py +++ b/gcloud/contrib/template_market/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.15 on 2024-12-06 06:47 +# Generated by Django 3.2.15 on 2024-12-09 12:51 from django.db import migrations, models @@ -15,7 +15,11 @@ class Migration(migrations.Migration): fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ("project_id", models.IntegerField(default=-1, help_text="项目 ID", verbose_name="项目 ID")), - ("template_id", models.IntegerField(db_index=True, help_text="模版 ID", verbose_name="模版 ID")), + ("template_id", models.IntegerField(help_text="模版 ID", verbose_name="模版 ID")), + ( + "scene_instance_id", + models.CharField(db_index=True, help_text="场景实例 ID", max_length=32, verbose_name="场景实例 ID"), + ), ("creator", models.CharField(default="", max_length=32, verbose_name="创建者")), ("create_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")), ("extra_info", models.JSONField(blank=True, null=True, verbose_name="额外信息")), diff --git a/gcloud/contrib/template_market/models.py b/gcloud/contrib/template_market/models.py index 39713bd8fc..26c708585a 100644 --- a/gcloud/contrib/template_market/models.py +++ b/gcloud/contrib/template_market/models.py @@ -18,7 +18,8 @@ class TemplateSharedRecord(models.Model): project_id = models.IntegerField(_("项目 ID"), default=-1, help_text="项目 ID") - template_id = models.IntegerField(_("模版 ID"), db_index=True, help_text="模版 ID") + template_id = models.IntegerField(_("模版 ID"), help_text="模版 ID") + scene_instance_id = models.CharField(_("场景实例 ID"), max_length=32, db_index=True, help_text="场景实例 ID") creator = models.CharField(_("创建者"), max_length=32, default="") create_at = models.DateTimeField(_("创建时间"), auto_now_add=True) extra_info = models.JSONField(_("额外信息"), blank=True, null=True) @@ -26,3 +27,18 @@ class TemplateSharedRecord(models.Model): class Meta: verbose_name = _("模板共享记录 TemplateSharedRecord") verbose_name_plural = _("模板共享记录 TemplateSharedRecord") + + @classmethod + def create(cls, project_id, template_id, scene_instance_id, creator="", extra_info=None): + if not scene_instance_id: + raise ValueError("场景实例 ID 不能为空") + + instance = cls( + project_id=project_id, + template_id=template_id, + scene_instance_id=scene_instance_id, + creator=creator, + extra_info=extra_info, + ) + instance.save() + return instance diff --git a/gcloud/contrib/template_market/permission.py b/gcloud/contrib/template_market/permission.py new file mode 100644 index 0000000000..434d503a47 --- /dev/null +++ b/gcloud/contrib/template_market/permission.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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. +""" +import logging + +from rest_framework import permissions + +from gcloud.conf import settings +from gcloud.contrib.template_market.models import TemplateSharedRecord + + +class TemplatePreviewPermission(permissions.BasePermission): + def has_permission(self, request, view): + template_id = request.GET.get("template_id") + project_id = request.GET.get("project_id") + + if not template_id or not project_id: + logging.warning("Missing required parameters.") + return False + + record = TemplateSharedRecord.objects.filter(template_id=template_id, project_id=project_id).first() + if record is None: + logging.warning("template_id {} does not exist.".format(template_id)) + return False + + return True + + +class SharedProcessTemplatePermission(permissions.BasePermission): + def has_permission(self, request, view): + return settings.ENABLE_TEMPLATE_MARKET diff --git a/gcloud/contrib/template_market/serializers.py b/gcloud/contrib/template_market/serializers.py index 738fa6e806..ab9bef6c20 100644 --- a/gcloud/contrib/template_market/serializers.py +++ b/gcloud/contrib/template_market/serializers.py @@ -10,20 +10,60 @@ 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 json from rest_framework import serializers from gcloud.constants import DATETIME_FORMAT from gcloud.contrib.template_market.models import TemplateSharedRecord +from gcloud.tasktmpl3.models import TaskTemplate + +class TemplatePreviewSerializer(serializers.ModelSerializer): + name = serializers.CharField(read_only=True, help_text="模板名称") + pipeline_tree = serializers.SerializerMethodField(read_only=True, help_text="pipeline_tree") -class TemplateSharedRecordSerializer(serializers.ModelSerializer): + def get_pipeline_tree(self, obj): + return json.dumps(obj.pipeline_tree) + + class Meta: + model = TaskTemplate + fields = ["name", "pipeline_tree"] + + +class TemplateSharedRecordSerializer(serializers.Serializer): template_id = serializers.CharField(required=True, help_text="模板id") project_id = serializers.CharField(required=True, help_text="项目id") + name = serializers.CharField(required=True, help_text="场景名称") + code = serializers.CharField(required=True, help_text="场景标识") + category = serializers.CharField(required=True, help_text="场景分类") + risk_level = serializers.IntegerField(required=True, help_text="风险级别") + labels = serializers.ListField(child=serializers.IntegerField(), required=True, help_text="场景标签列表") + usage_content = serializers.CharField(required=True, help_text="使用说明") creator = serializers.CharField(required=False, max_length=32, help_text="创建者") create_at = serializers.DateTimeField(required=False, help_text="创建时间", format=DATETIME_FORMAT) extra_info = serializers.JSONField(required=False, allow_null=True, help_text="额外信息") + def create(self, validated_data): + instance = TemplateSharedRecord.objects.create( + project_id=validated_data["project_id"], + template_id=validated_data["template_id"], + creator=validated_data.get("creator", ""), + extra_info=validated_data.get("extra_info"), + ) + return instance + class Meta: model = TemplateSharedRecord - fields = ["project_id", "template_id", "creator", "create_at", "extra_info"] + fields = [ + "project_id", + "template_id", + "creator", + "create_at", + "extra_info", + "name", + "code", + "category", + "risk_level", + "labels", + "usage_content", + ] diff --git a/gcloud/contrib/template_market/urls.py b/gcloud/contrib/template_market/urls.py index 5a3a866805..4b9ddd09bf 100644 --- a/gcloud/contrib/template_market/urls.py +++ b/gcloud/contrib/template_market/urls.py @@ -13,13 +13,13 @@ from django.conf.urls import include, url from rest_framework.routers import DefaultRouter -from gcloud.contrib.template_market.viewsets import StoreTemplateViewSet, SharedProcessTemplateViewSet +from gcloud.contrib.template_market.viewsets import TemplatePreviewViewSet, SharedProcessTemplateViewSet -drf_api = DefaultRouter() -drf_api.register(r"store_templates", StoreTemplateViewSet) -drf_api.register(r"shared_process_templates", SharedProcessTemplateViewSet) +template_market_router = DefaultRouter() +template_market_router.register(r"template_preview", TemplatePreviewViewSet) +template_market_router.register(r"shared_process_templates", SharedProcessTemplateViewSet) urlpatterns = [ - url(r"^api/", include(drf_api.urls)), + url(r"^api/", include(template_market_router.urls)), ] diff --git a/gcloud/contrib/template_market/viewsets.py b/gcloud/contrib/template_market/viewsets.py index f6c7acbafc..e85adf183b 100644 --- a/gcloud/contrib/template_market/viewsets.py +++ b/gcloud/contrib/template_market/viewsets.py @@ -10,57 +10,36 @@ 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 json +import requests import logging from rest_framework import viewsets from rest_framework.response import Response from rest_framework import permissions +from drf_yasg.utils import swagger_auto_schema from gcloud.conf import settings from gcloud import err_code -from gcloud.contrib.template_market.serializers import TemplateSharedRecordSerializer +from gcloud.contrib.template_market.serializers import TemplateSharedRecordSerializer, TemplatePreviewSerializer from gcloud.contrib.template_market.models import TemplateSharedRecord -from gcloud.core.apis.drf.serilaziers.task_template import TaskTemplateSerializer from gcloud.taskflow3.models import TaskTemplate +from gcloud.contrib.template_market.permission import TemplatePreviewPermission, SharedProcessTemplatePermission -class StoreTemplatePermission(permissions.BasePermission): - def has_permission(self, request, view): - template_id = view.kwargs.get("pk") - project_id = request.GET.get("project_id") - - if not template_id or not project_id: - logging.warning("Missing required parameters.") - return False - try: - TemplateSharedRecord.objects.get(template_id=template_id, project_id=project_id) - except Exception: - logging.warning("template_id {} does not exist.".format(template_id)) - return False - return True - - -class SharedProcessTemplatePermission(permissions.BasePermission): - def has_permission(self, request, view): - if not settings.ENABLE_TEMPLATE_MARKET: - return False - return True - - -class StoreTemplateViewSet(viewsets.ViewSet): +class TemplatePreviewViewSet(viewsets.ViewSet): queryset = TaskTemplate.objects.filter(pipeline_template__isnull=False, is_deleted=False) - serializer_class = TaskTemplateSerializer - permission_classes = [permissions.IsAuthenticated, StoreTemplatePermission] + serializer_class = TemplatePreviewSerializer + permission_classes = [permissions.IsAuthenticated, TemplatePreviewPermission] def retrieve(self, request, *args, **kwargs): - instance = self.queryset.get(id=kwargs["pk"], project_id=request.GET.get("project_id")) + instance = self.queryset.get(id=request.GET.get("template_id"), project_id=request.GET.get("project_id")) serializer = self.serializer_class(instance) - return Response(serializer.data) + return Response({"result": True, "data": serializer.data, "code": err_code.SUCCESS.code}) -class SharedProcessTemplateViewSet(viewsets.ModelViewSet): +class SharedProcessTemplateViewSet(viewsets.ViewSet): queryset = TemplateSharedRecord.objects.all() serializer_class = TemplateSharedRecordSerializer permission_classes = [permissions.IsAuthenticated, SharedProcessTemplatePermission] @@ -69,33 +48,90 @@ def _get_market_routing(self, market_url): return f"{settings.TEMPLATE_MARKET_API_URL}/{market_url}" def retrieve(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.GET) - serializer.is_valid(raise_exception=True) - - project_id = serializer.validated_data.get("project_id") - template_id = serializer.validated_data.get("template_id") - - data = {"project_id": project_id, "template_id": template_id} - - # todo: 调用第三方接口查询信息 - - return Response({"result": True, "data": data, "code": err_code.SUCCESS.code}) - + project_id = request.GET.get("project_id") + template_id = request.GET.get("template_id") + + template_shared_obj = TemplateSharedRecord.objects.filter( + project_id=project_id, template_id=template_id + ).first() + + if not template_shared_obj: + logging.exception(f"Template shared record not found, project_id: {project_id}, template_id: {template_id}") + return Response( + { + "result": False, + "message": "Template shared record not found", + "code": err_code.CONTENT_NOT_EXIST.code, + } + ) + url = self._get_market_routing(f"sre_scene/flow_template_scene/{template_shared_obj.scene_instance_id}/") + result = requests.get(url=url) + if not result.status_code == 200: + logging.exception(f"Get template information from market failed, error code: {result.status_code}") + return Response( + {"result": False, "message": "Get template information failed", "code": err_code.OPERATION_FAIL.code} + ) + + return Response({"result": True, "data": result.json(), "code": err_code.SUCCESS.code}) + + @swagger_auto_schema(request_body=TemplateSharedRecordSerializer) def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) + serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) - project_id = serializer.validated_data.get("project_id") - template_id = serializer.validated_data.get("template_id") - + project_id = serializer.validated_data["project_id"] + template_id = serializer.validated_data["template_id"] + + task_template_obj = TaskTemplate.objects.filter(project_id=project_id, id=template_id).first() + if not task_template_obj: + logging.exception(f"Template with project_id {project_id} and template_id {template_id} not found.") + return Response({"result": False, "message": "Template not found", "code": err_code.CONTENT_NOT_EXIST.code}) + + if TemplateSharedRecord.objects.filter(project_id=project_id, template_id=template_id).exists(): + logging.warning(f"Template with project_id {project_id} and template_id {template_id} has been shared.") + return Response( + {"result": False, "message": "Template has been shared", "code": err_code.OPERATION_FAIL.code} + ) + + url = self._get_market_routing("sre_scene/flow_template_scene/") + + data = { + "name": serializer.validated_data["name"], + "code": serializer.validated_data["code"], + "category": serializer.validated_data["category"], + "risk_level": serializer.validated_data["risk_level"], + "labels": serializer.validated_data["labels"], + "source_system": "bk_sops", + "project_code": project_id, + "templates": json.dumps([{"id": template_id, "name": task_template_obj.name}]), + "usage_content": {"content": serializer.validated_data["usage_content"]}, + } try: - TaskTemplate.objects.get(project_id=project_id, id=template_id) - except TaskTemplate.DoesNotExist: - logging.warning(f"Template with project_id {project_id} and template_id {template_id} not found.") - return Response({"result": False, "message": "Template not found", "code": err_code.OPERATION_FAIL.code}) - - # todo: 调用第三方接口实现共享 - - self.perform_create(serializer) - - return Response({"result": True, "message": "Share template successfully", "code": err_code.SUCCESS.code}) + result = requests.post(url, data=data) + if result.status_code != 200: + return Response( + { + "result": False, + "message": "Failed to share template to sre store", + "code": err_code.OPERATION_FAIL.code, + } + ) + response_data = result.json() + TemplateSharedRecord.create( + project_id=project_id, + template_id=template_id, + scene_instance_id=response_data["data"]["id"], + creator=serializer.validated_data.get("creator"), + extra_info=serializer.validated_data.get("extra_info"), + ) + return Response( + { + "result": True, + "data": response_data, + "message": "Share template successfully", + "code": err_code.SUCCESS.code, + } + ) + except Exception as e: + logging.exception("Share template failed: %s", e) + return Response({"result": False, "message": "Share template failed", "code": err_code.OPERATION_FAIL.code}) From 113c6d6c88838e68ec299417df59aedfedb35524 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Tue, 10 Dec 2024 10:29:40 +0800 Subject: [PATCH 05/14] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E9=97=AE=E9=A2=98=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gcloud/contrib/template_market/admin.py | 6 ++-- .../migrations/0001_initial.py | 7 ++-- gcloud/contrib/template_market/models.py | 2 +- gcloud/contrib/template_market/serializers.py | 33 +------------------ 4 files changed, 7 insertions(+), 41 deletions(-) diff --git a/gcloud/contrib/template_market/admin.py b/gcloud/contrib/template_market/admin.py index b2d3c729ab..7a89f6a28f 100644 --- a/gcloud/contrib/template_market/admin.py +++ b/gcloud/contrib/template_market/admin.py @@ -18,6 +18,6 @@ @admin.register(models.TemplateSharedRecord) class TemplateMarketAdmin(admin.ModelAdmin): - list_display = ["project_id", "template_id", "creator", "create_at", "extra_info"] - list_filter = ["project_id", "template_id", "creator", "create_at"] - search_fields = ["project_id", "creator"] + list_display = ["project_id", "template_id", "scene_instance_id", "creator", "create_at", "extra_info"] + list_filter = ["project_id", "creator", "create_at"] + search_fields = ["project_id", "template_id", "scene_instance_id", "creator"] diff --git a/gcloud/contrib/template_market/migrations/0001_initial.py b/gcloud/contrib/template_market/migrations/0001_initial.py index c9e5d1f27d..e9fb9abbe1 100644 --- a/gcloud/contrib/template_market/migrations/0001_initial.py +++ b/gcloud/contrib/template_market/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.15 on 2024-12-09 12:51 +# Generated by Django 3.2.15 on 2024-12-10 02:27 from django.db import migrations, models @@ -16,10 +16,7 @@ class Migration(migrations.Migration): ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ("project_id", models.IntegerField(default=-1, help_text="项目 ID", verbose_name="项目 ID")), ("template_id", models.IntegerField(help_text="模版 ID", verbose_name="模版 ID")), - ( - "scene_instance_id", - models.CharField(db_index=True, help_text="场景实例 ID", max_length=32, verbose_name="场景实例 ID"), - ), + ("scene_instance_id", models.IntegerField(db_index=True, help_text="场景实例 ID", verbose_name="场景实例 ID")), ("creator", models.CharField(default="", max_length=32, verbose_name="创建者")), ("create_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")), ("extra_info", models.JSONField(blank=True, null=True, verbose_name="额外信息")), diff --git a/gcloud/contrib/template_market/models.py b/gcloud/contrib/template_market/models.py index 26c708585a..9ca0741617 100644 --- a/gcloud/contrib/template_market/models.py +++ b/gcloud/contrib/template_market/models.py @@ -19,7 +19,7 @@ class TemplateSharedRecord(models.Model): project_id = models.IntegerField(_("项目 ID"), default=-1, help_text="项目 ID") template_id = models.IntegerField(_("模版 ID"), help_text="模版 ID") - scene_instance_id = models.CharField(_("场景实例 ID"), max_length=32, db_index=True, help_text="场景实例 ID") + scene_instance_id = models.IntegerField(_("场景实例 ID"), db_index=True, help_text="场景实例 ID") creator = models.CharField(_("创建者"), max_length=32, default="") create_at = models.DateTimeField(_("创建时间"), auto_now_add=True) extra_info = models.JSONField(_("额外信息"), blank=True, null=True) diff --git a/gcloud/contrib/template_market/serializers.py b/gcloud/contrib/template_market/serializers.py index ab9bef6c20..9e56588150 100644 --- a/gcloud/contrib/template_market/serializers.py +++ b/gcloud/contrib/template_market/serializers.py @@ -14,21 +14,15 @@ from rest_framework import serializers from gcloud.constants import DATETIME_FORMAT -from gcloud.contrib.template_market.models import TemplateSharedRecord -from gcloud.tasktmpl3.models import TaskTemplate -class TemplatePreviewSerializer(serializers.ModelSerializer): +class TemplatePreviewSerializer(serializers.Serializer): name = serializers.CharField(read_only=True, help_text="模板名称") pipeline_tree = serializers.SerializerMethodField(read_only=True, help_text="pipeline_tree") def get_pipeline_tree(self, obj): return json.dumps(obj.pipeline_tree) - class Meta: - model = TaskTemplate - fields = ["name", "pipeline_tree"] - class TemplateSharedRecordSerializer(serializers.Serializer): template_id = serializers.CharField(required=True, help_text="模板id") @@ -42,28 +36,3 @@ class TemplateSharedRecordSerializer(serializers.Serializer): creator = serializers.CharField(required=False, max_length=32, help_text="创建者") create_at = serializers.DateTimeField(required=False, help_text="创建时间", format=DATETIME_FORMAT) extra_info = serializers.JSONField(required=False, allow_null=True, help_text="额外信息") - - def create(self, validated_data): - instance = TemplateSharedRecord.objects.create( - project_id=validated_data["project_id"], - template_id=validated_data["template_id"], - creator=validated_data.get("creator", ""), - extra_info=validated_data.get("extra_info"), - ) - return instance - - class Meta: - model = TemplateSharedRecord - fields = [ - "project_id", - "template_id", - "creator", - "create_at", - "extra_info", - "name", - "code", - "category", - "risk_level", - "labels", - "usage_content", - ] From 3f16485ca871dbee6f4de270ab8f7a0037e3cb33 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Tue, 10 Dec 2024 11:42:43 +0800 Subject: [PATCH 06/14] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E5=86=97?= =?UTF-8?q?=E4=BD=99=E5=88=A4=E6=96=AD=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gcloud/contrib/template_market/viewsets.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/gcloud/contrib/template_market/viewsets.py b/gcloud/contrib/template_market/viewsets.py index e85adf183b..6ff09df957 100644 --- a/gcloud/contrib/template_market/viewsets.py +++ b/gcloud/contrib/template_market/viewsets.py @@ -87,12 +87,6 @@ def create(self, request, *args, **kwargs): logging.exception(f"Template with project_id {project_id} and template_id {template_id} not found.") return Response({"result": False, "message": "Template not found", "code": err_code.CONTENT_NOT_EXIST.code}) - if TemplateSharedRecord.objects.filter(project_id=project_id, template_id=template_id).exists(): - logging.warning(f"Template with project_id {project_id} and template_id {template_id} has been shared.") - return Response( - {"result": False, "message": "Template has been shared", "code": err_code.OPERATION_FAIL.code} - ) - url = self._get_market_routing("sre_scene/flow_template_scene/") data = { From 5877bb64299ec4ee9a856f6a401561ab70073efa Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Tue, 10 Dec 2024 12:21:33 +0800 Subject: [PATCH 07/14] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=88=A4=E6=96=AD=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gcloud/contrib/template_market/viewsets.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gcloud/contrib/template_market/viewsets.py b/gcloud/contrib/template_market/viewsets.py index 6ff09df957..b9aeb87fe8 100644 --- a/gcloud/contrib/template_market/viewsets.py +++ b/gcloud/contrib/template_market/viewsets.py @@ -66,13 +66,14 @@ def retrieve(self, request, *args, **kwargs): ) url = self._get_market_routing(f"sre_scene/flow_template_scene/{template_shared_obj.scene_instance_id}/") result = requests.get(url=url) - if not result.status_code == 200: + response_data = result.json() + if not response_data["result"]: logging.exception(f"Get template information from market failed, error code: {result.status_code}") return Response( {"result": False, "message": "Get template information failed", "code": err_code.OPERATION_FAIL.code} ) - return Response({"result": True, "data": result.json(), "code": err_code.SUCCESS.code}) + return Response({"result": True, "data": response_data, "code": err_code.SUCCESS.code}) @swagger_auto_schema(request_body=TemplateSharedRecordSerializer) def create(self, request, *args, **kwargs): @@ -102,7 +103,8 @@ def create(self, request, *args, **kwargs): } try: result = requests.post(url, data=data) - if result.status_code != 200: + response_data = result.json() + if not response_data["result"]: return Response( { "result": False, @@ -110,7 +112,6 @@ def create(self, request, *args, **kwargs): "code": err_code.OPERATION_FAIL.code, } ) - response_data = result.json() TemplateSharedRecord.create( project_id=project_id, template_id=template_id, From 48aa0d9405b2b044c75243c028f5afe6232d4451 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Wed, 11 Dec 2024 21:52:52 +0800 Subject: [PATCH 08/14] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=9D=83?= =?UTF-8?q?=E9=99=90=E9=97=AE=E9=A2=98=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/default.py | 2 + env.py | 2 + .../commands/data/api-resources.yml | 2 +- .../copy_template_across_project.py | 5 +- gcloud/contrib/template_market/admin.py | 8 +- .../migrations/0001_initial.py | 7 +- gcloud/contrib/template_market/models.py | 21 +-- gcloud/contrib/template_market/permission.py | 29 +++- gcloud/contrib/template_market/serializers.py | 72 +++++++- gcloud/contrib/template_market/utils.py | 39 +++++ gcloud/contrib/template_market/viewsets.py | 157 +++++++++++------- 11 files changed, 245 insertions(+), 99 deletions(-) create mode 100644 gcloud/contrib/template_market/utils.py diff --git a/config/default.py b/config/default.py index be7bbd0adb..c771f2f3f2 100644 --- a/config/default.py +++ b/config/default.py @@ -890,3 +890,5 @@ def check_engine_admin_permission(request, *args, **kwargs): ENABLE_TEMPLATE_MARKET = env.ENABLE_TEMPLATE_MARKET # 流程商店 API 地址 TEMPLATE_MARKET_API_URL = env.TEMPLATE_MARKET_API_URL +# 共享流程最大数量 +MAX_NUMBER_SHARED_PROCESSES = env.MAX_NUMBER_SHARED_PROCESSES diff --git a/env.py b/env.py index 2a4e1546ed..cab2ee11d1 100644 --- a/env.py +++ b/env.py @@ -158,3 +158,5 @@ ENABLE_TEMPLATE_MARKET = False if os.getenv("ENABLE_TEMPLATE_MARKET") is None else True # 流程商店 API 地址 TEMPLATE_MARKET_API_URL = os.getenv("TEMPLATE_MARKET_API_URL", "") +# 共享流程最大数量 +MAX_NUMBER_SHARED_PROCESSES = int(os.getenv("MAX_NUMBER_SHARED_PROCESSES", 10)) diff --git a/gcloud/apigw/management/commands/data/api-resources.yml b/gcloud/apigw/management/commands/data/api-resources.yml index d76ef16d2c..cdabd38921 100644 --- a/gcloud/apigw/management/commands/data/api-resources.yml +++ b/gcloud/apigw/management/commands/data/api-resources.yml @@ -767,7 +767,7 @@ paths: default: description: '' x-bk-apigateway-resource: - isPublic: true + isPublic: false allowApplyPermission: true matchSubpath: false backend: diff --git a/gcloud/apigw/validators/copy_template_across_project.py b/gcloud/apigw/validators/copy_template_across_project.py index 27eaa1cc1f..53d6094773 100644 --- a/gcloud/apigw/validators/copy_template_across_project.py +++ b/gcloud/apigw/validators/copy_template_across_project.py @@ -22,7 +22,8 @@ def validate(self, request, *args, **kwargs): if not valid: return valid, err - if not json.loads(request.body).get("new_project_id") or not json.loads(request.body).get("template_id"): - return False, "new_project_id and template_id is required" + data = json.loads(request.body) + if not data.get("new_project_id") or not data.get("template_id"): + return False, "new_project_id and template_id are required" return True, "" diff --git a/gcloud/contrib/template_market/admin.py b/gcloud/contrib/template_market/admin.py index 7a89f6a28f..9083b78bdd 100644 --- a/gcloud/contrib/template_market/admin.py +++ b/gcloud/contrib/template_market/admin.py @@ -17,7 +17,7 @@ @admin.register(models.TemplateSharedRecord) -class TemplateMarketAdmin(admin.ModelAdmin): - list_display = ["project_id", "template_id", "scene_instance_id", "creator", "create_at", "extra_info"] - list_filter = ["project_id", "creator", "create_at"] - search_fields = ["project_id", "template_id", "scene_instance_id", "creator"] +class TemplateSharedRecordAdmin(admin.ModelAdmin): + list_display = ["scene_shared_id", "project_id", "templates", "creator", "create_at", "update_at", "extra_info"] + list_filter = ["project_id", "creator", "create_at", "update_at"] + search_fields = ["scene_shared_id", "project_id", "creator"] diff --git a/gcloud/contrib/template_market/migrations/0001_initial.py b/gcloud/contrib/template_market/migrations/0001_initial.py index e9fb9abbe1..50b0871bde 100644 --- a/gcloud/contrib/template_market/migrations/0001_initial.py +++ b/gcloud/contrib/template_market/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.15 on 2024-12-10 02:27 +# Generated by Django 3.2.15 on 2024-12-11 13:46 from django.db import migrations, models @@ -14,11 +14,12 @@ class Migration(migrations.Migration): name="TemplateSharedRecord", fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("scene_shared_id", models.IntegerField(help_text="共享实例id", verbose_name="共享实例id")), ("project_id", models.IntegerField(default=-1, help_text="项目 ID", verbose_name="项目 ID")), - ("template_id", models.IntegerField(help_text="模版 ID", verbose_name="模版 ID")), - ("scene_instance_id", models.IntegerField(db_index=True, help_text="场景实例 ID", verbose_name="场景实例 ID")), + ("templates", models.JSONField(help_text="模板 ID 列表", verbose_name="模板 ID 列表")), ("creator", models.CharField(default="", max_length=32, verbose_name="创建者")), ("create_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")), + ("update_at", models.DateTimeField(auto_now=True, verbose_name="更新时间")), ("extra_info", models.JSONField(blank=True, null=True, verbose_name="额外信息")), ], options={ diff --git a/gcloud/contrib/template_market/models.py b/gcloud/contrib/template_market/models.py index 9ca0741617..f16eaf7ab3 100644 --- a/gcloud/contrib/template_market/models.py +++ b/gcloud/contrib/template_market/models.py @@ -11,34 +11,19 @@ specific language governing permissions and limitations under the License. """ - from django.db import models from django.utils.translation import ugettext_lazy as _ class TemplateSharedRecord(models.Model): + scene_shared_id = models.IntegerField(_("共享实例id"), help_text="共享实例id") project_id = models.IntegerField(_("项目 ID"), default=-1, help_text="项目 ID") - template_id = models.IntegerField(_("模版 ID"), help_text="模版 ID") - scene_instance_id = models.IntegerField(_("场景实例 ID"), db_index=True, help_text="场景实例 ID") + templates = models.JSONField(_("模板 ID 列表"), help_text="模板 ID 列表") creator = models.CharField(_("创建者"), max_length=32, default="") create_at = models.DateTimeField(_("创建时间"), auto_now_add=True) + update_at = models.DateTimeField(verbose_name=_("更新时间"), auto_now=True) extra_info = models.JSONField(_("额外信息"), blank=True, null=True) class Meta: verbose_name = _("模板共享记录 TemplateSharedRecord") verbose_name_plural = _("模板共享记录 TemplateSharedRecord") - - @classmethod - def create(cls, project_id, template_id, scene_instance_id, creator="", extra_info=None): - if not scene_instance_id: - raise ValueError("场景实例 ID 不能为空") - - instance = cls( - project_id=project_id, - template_id=template_id, - scene_instance_id=scene_instance_id, - creator=creator, - extra_info=extra_info, - ) - instance.save() - return instance diff --git a/gcloud/contrib/template_market/permission.py b/gcloud/contrib/template_market/permission.py index 434d503a47..b7720f852f 100644 --- a/gcloud/contrib/template_market/permission.py +++ b/gcloud/contrib/template_market/permission.py @@ -12,24 +12,29 @@ """ import logging +from django.db.models import Q from rest_framework import permissions from gcloud.conf import settings from gcloud.contrib.template_market.models import TemplateSharedRecord +from gcloud.iam_auth import IAMMeta +from gcloud.iam_auth.utils import iam_multi_resource_auth_or_raise class TemplatePreviewPermission(permissions.BasePermission): def has_permission(self, request, view): - template_id = request.GET.get("template_id") - project_id = request.GET.get("project_id") - - if not template_id or not project_id: - logging.warning("Missing required parameters.") + try: + template_id = int(request.GET.get("template_id")) + project_id = int(request.GET.get("project_id")) + except (TypeError, ValueError): + logging.warning("Missing or invalid required parameters.") return False - record = TemplateSharedRecord.objects.filter(template_id=template_id, project_id=project_id).first() + record = TemplateSharedRecord.objects.filter( + Q(project_id=project_id) & Q(templates__contains=[template_id]) + ).first() if record is None: - logging.warning("template_id {} does not exist.".format(template_id)) + logging.warning("The specified template could not be found") return False return True @@ -37,4 +42,14 @@ def has_permission(self, request, view): class SharedProcessTemplatePermission(permissions.BasePermission): def has_permission(self, request, view): + username = request.user.username + serializer = view.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + + template_id_list = [template.get("id") for template in serializer.validated_data["templates"]] + if view.action in ["create", "partial_update"]: + iam_multi_resource_auth_or_raise( + username, IAMMeta.FLOW_EDIT_ACTION, template_id_list, "resources_list_for_flows" + ) + return settings.ENABLE_TEMPLATE_MARKET diff --git a/gcloud/contrib/template_market/serializers.py b/gcloud/contrib/template_market/serializers.py index 9e56588150..baa8875001 100644 --- a/gcloud/contrib/template_market/serializers.py +++ b/gcloud/contrib/template_market/serializers.py @@ -11,9 +11,11 @@ specific language governing permissions and limitations under the License. """ import json +import logging from rest_framework import serializers from gcloud.constants import DATETIME_FORMAT +from gcloud.contrib.template_market.models import TemplateSharedRecord class TemplatePreviewSerializer(serializers.Serializer): @@ -24,15 +26,71 @@ def get_pipeline_tree(self, obj): return json.dumps(obj.pipeline_tree) -class TemplateSharedRecordSerializer(serializers.Serializer): +class TemplateProjectBaseSerializer(serializers.Serializer): template_id = serializers.CharField(required=True, help_text="模板id") project_id = serializers.CharField(required=True, help_text="项目id") - name = serializers.CharField(required=True, help_text="场景名称") - code = serializers.CharField(required=True, help_text="场景标识") - category = serializers.CharField(required=True, help_text="场景分类") - risk_level = serializers.IntegerField(required=True, help_text="风险级别") - labels = serializers.ListField(child=serializers.IntegerField(), required=True, help_text="场景标签列表") - usage_content = serializers.CharField(required=True, help_text="使用说明") + + +class TemplateSharedRecordSerializer(serializers.ModelSerializer): + project_id = serializers.CharField(required=True, max_length=32, help_text="项目id") + templates = serializers.ListField(required=True, help_text="关联的模板列表") creator = serializers.CharField(required=False, max_length=32, help_text="创建者") create_at = serializers.DateTimeField(required=False, help_text="创建时间", format=DATETIME_FORMAT) + update_at = serializers.DateTimeField(required=False, help_text="更新时间", format=DATETIME_FORMAT) extra_info = serializers.JSONField(required=False, allow_null=True, help_text="额外信息") + id = serializers.IntegerField(required=False, help_text="共享实例id") + name = serializers.CharField(required=True, help_text="共享名称") + code = serializers.CharField(required=True, help_text="共享标识") + category = serializers.CharField(required=True, help_text="共享分类") + risk_level = serializers.IntegerField(required=True, help_text="风险级别") + usage_id = serializers.IntegerField(required=True, help_text="使用说明id") + labels = serializers.ListField(child=serializers.IntegerField(), required=True, help_text="共享标签列表") + usage_content = serializers.JSONField(required=True, help_text="使用说明") + + class Meta: + model = TemplateSharedRecord + fields = [ + "project_id", + "templates", + "creator", + "create_at", + "update_at", + "extra_info", + "labels", + "usage_content", + "id", + "name", + "code", + "category", + "risk_level", + "usage_id", + ] + + def convert_templates(self, templates): + return [template.get("id") for template in templates] + + def create(self, validated_data): + try: + validated_data["templates"] = self.convert_templates(validated_data["templates"]) + return TemplateSharedRecord.objects.create( + scene_shared_id=validated_data["id"], + project_id=validated_data["project_id"], + templates=validated_data["templates"], + creator=validated_data["creator"], + extra_info=validated_data["extra_info"], + ) + except Exception: + logging.exception("Failed to create model sharing record") + raise Exception("Failed to create model sharing record") + + def update(self, instance, validated_data): + try: + validated_data["templates"] = self.convert_templates(validated_data["templates"]) + instance.project_id = validated_data["project_id"] + instance.templates = validated_data["templates"] + instance.creator = validated_data["creator"] + instance.extra_info = validated_data["extra_info"] + instance.save() + except Exception: + logging.exception("Failed to update model sharing record") + raise Exception("Failed to update model sharing record") diff --git a/gcloud/contrib/template_market/utils.py b/gcloud/contrib/template_market/utils.py new file mode 100644 index 0000000000..359b333fdd --- /dev/null +++ b/gcloud/contrib/template_market/utils.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017 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. +""" + +import requests + +from gcloud.conf import settings + + +class MarketAPIClient: + def __init__(self): + self.base_url = settings.TEMPLATE_MARKET_API_URL + + def _get_url(self, endpoint): + return f"{self.base_url}{endpoint}" + + def get_template_list(self): + url = self._get_url("/sre_scene/flow_template_scene/") + response = requests.get(url) + return response.json() + + def create_template(self, data): + url = self._get_url("/sre_scene/flow_template_scene/") + response = requests.post(url, json=data) + return response.json() + + def patch_template(self, data, scene_shared_id): + url = self._get_url(f"/sre_scene/flow_template_scene/{scene_shared_id}/") + response = requests.patch(url, json=data) + return response.json() diff --git a/gcloud/contrib/template_market/viewsets.py b/gcloud/contrib/template_market/viewsets.py index b9aeb87fe8..e35d2f3d02 100644 --- a/gcloud/contrib/template_market/viewsets.py +++ b/gcloud/contrib/template_market/viewsets.py @@ -11,19 +11,23 @@ specific language governing permissions and limitations under the License. """ import json -import requests import logging from rest_framework import viewsets from rest_framework.response import Response from rest_framework import permissions -from drf_yasg.utils import swagger_auto_schema -from gcloud.conf import settings from gcloud import err_code -from gcloud.contrib.template_market.serializers import TemplateSharedRecordSerializer, TemplatePreviewSerializer +from gcloud.conf import settings +from drf_yasg.utils import swagger_auto_schema +from gcloud.contrib.template_market.serializers import ( + TemplateSharedRecordSerializer, + TemplatePreviewSerializer, + TemplateProjectBaseSerializer, +) from gcloud.contrib.template_market.models import TemplateSharedRecord from gcloud.taskflow3.models import TaskTemplate +from gcloud.contrib.template_market.utils import MarketAPIClient from gcloud.contrib.template_market.permission import TemplatePreviewPermission, SharedProcessTemplatePermission @@ -33,7 +37,13 @@ class TemplatePreviewViewSet(viewsets.ViewSet): permission_classes = [permissions.IsAuthenticated, TemplatePreviewPermission] def retrieve(self, request, *args, **kwargs): - instance = self.queryset.get(id=request.GET.get("template_id"), project_id=request.GET.get("project_id")) + request_serializer = TemplateProjectBaseSerializer(data=request.GET) + request_serializer.is_valid(raise_exception=True) + + template_id = request_serializer.validated_data["template_id"] + project_id = request_serializer.validated_data["project_id"] + + instance = self.queryset.get(id=template_id, project_id=project_id) serializer = self.serializer_class(instance) return Response({"result": True, "data": serializer.data, "code": err_code.SUCCESS.code}) @@ -44,31 +54,46 @@ class SharedProcessTemplateViewSet(viewsets.ViewSet): serializer_class = TemplateSharedRecordSerializer permission_classes = [permissions.IsAuthenticated, SharedProcessTemplatePermission] - def _get_market_routing(self, market_url): - return f"{settings.TEMPLATE_MARKET_API_URL}/{market_url}" + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.market_client = MarketAPIClient() - def retrieve(self, request, *args, **kwargs): - project_id = request.GET.get("project_id") - template_id = request.GET.get("template_id") + def _build_template_data(self, serializer): - template_shared_obj = TemplateSharedRecord.objects.filter( - project_id=project_id, template_id=template_id - ).first() + data = { + "name": serializer.validated_data["name"], + "code": serializer.validated_data["code"], + "category": serializer.validated_data["category"], + "risk_level": serializer.validated_data["risk_level"], + "usage_id": serializer.validated_data["usage_id"], + "labels": serializer.validated_data["labels"], + "source_system": "bk_sops", + "project_code": serializer.validated_data["project_id"], + "templates": json.dumps(serializer.validated_data["templates"]), + "usage_content": serializer.validated_data["usage_content"], + } + scene_shared_id = serializer.validated_data.get("id") + if scene_shared_id: + data["id"] = scene_shared_id + return data + + def _get_processes_count(self, templates): + template_id_list = [template.get("id") for template in templates] + + template_objs = TaskTemplate.objects.filter(id__in=template_id_list) + + total_count = 0 + for template in template_objs: + activities = template.pipeline_tree.get("activities", []) + total_count += len(activities) + + return total_count + + def list(self, request, *args, **kwargs): + response_data = self.market_client.get_template_list() - if not template_shared_obj: - logging.exception(f"Template shared record not found, project_id: {project_id}, template_id: {template_id}") - return Response( - { - "result": False, - "message": "Template shared record not found", - "code": err_code.CONTENT_NOT_EXIST.code, - } - ) - url = self._get_market_routing(f"sre_scene/flow_template_scene/{template_shared_obj.scene_instance_id}/") - result = requests.get(url=url) - response_data = result.json() if not response_data["result"]: - logging.exception(f"Get template information from market failed, error code: {result.status_code}") + logging.exception(f"Get template information from market failed, error code: {response_data.get('code')}") return Response( {"result": False, "message": "Get template information failed", "code": err_code.OPERATION_FAIL.code} ) @@ -80,31 +105,20 @@ def create(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) - project_id = serializer.validated_data["project_id"] - template_id = serializer.validated_data["template_id"] - - task_template_obj = TaskTemplate.objects.filter(project_id=project_id, id=template_id).first() - if not task_template_obj: - logging.exception(f"Template with project_id {project_id} and template_id {template_id} not found.") - return Response({"result": False, "message": "Template not found", "code": err_code.CONTENT_NOT_EXIST.code}) - - url = self._get_market_routing("sre_scene/flow_template_scene/") + template_count = self._get_processes_count(serializer.validated_data["templates"]) + if template_count > settings.MAX_NUMBER_SHARED_PROCESSES: + return Response( + { + "result": False, + "message": "The number of selected templates exceeds the limit", + "code": err_code.OPERATION_FAIL.code, + } + ) - data = { - "name": serializer.validated_data["name"], - "code": serializer.validated_data["code"], - "category": serializer.validated_data["category"], - "risk_level": serializer.validated_data["risk_level"], - "labels": serializer.validated_data["labels"], - "source_system": "bk_sops", - "project_code": project_id, - "templates": json.dumps([{"id": template_id, "name": task_template_obj.name}]), - "usage_content": {"content": serializer.validated_data["usage_content"]}, - } + data = self._build_template_data(serializer) try: - result = requests.post(url, data=data) - response_data = result.json() - if not response_data["result"]: + response_data = self.market_client.create_template(data) + if not response_data.get("result"): return Response( { "result": False, @@ -112,17 +126,12 @@ def create(self, request, *args, **kwargs): "code": err_code.OPERATION_FAIL.code, } ) - TemplateSharedRecord.create( - project_id=project_id, - template_id=template_id, - scene_instance_id=response_data["data"]["id"], - creator=serializer.validated_data.get("creator"), - extra_info=serializer.validated_data.get("extra_info"), - ) + serializer.validated_data["id"] = response_data["data"]["id"] + serializer.create(serializer.validated_data) return Response( { "result": True, - "data": response_data, + "data": "response_data", "message": "Share template successfully", "code": err_code.SUCCESS.code, } @@ -130,3 +139,37 @@ def create(self, request, *args, **kwargs): except Exception as e: logging.exception("Share template failed: %s", e) return Response({"result": False, "message": "Share template failed", "code": err_code.OPERATION_FAIL.code}) + + @swagger_auto_schema(request_body=TemplateSharedRecordSerializer) + def partial_update(self, request, *args, **kwargs): + serializer = self.serializer_class(data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + + scene_shared_id = serializer.validated_data.get("id") + + data = self._build_template_data(serializer) + try: + response_data = self.market_client.patch_template(data, scene_shared_id) + if not response_data.get("result"): + return Response( + { + "result": False, + "message": "Update template to sre store failed", + "code": err_code.OPERATION_FAIL.code, + } + ) + instance = self.queryset.get(scene_shared_id=scene_shared_id) + serializer.update(instance, serializer.validated_data) + return Response( + { + "result": True, + "data": response_data, + "message": "Share template successfully", + "code": err_code.SUCCESS.code, + } + ) + except Exception as e: + logging.exception("Failed to update scene template: %s", e) + return Response( + {"result": False, "message": "Failed to update scene template", "code": err_code.OPERATION_FAIL.code} + ) From 964f0723320ee357dadbbac52a8034354287fea6 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Thu, 12 Dec 2024 09:48:46 +0800 Subject: [PATCH 09/14] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E9=97=AE=E9=A2=98=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gcloud/contrib/template_market/permission.py | 11 ++--- gcloud/contrib/template_market/serializers.py | 42 +++++-------------- gcloud/contrib/template_market/viewsets.py | 26 ++++++------ 3 files changed, 29 insertions(+), 50 deletions(-) diff --git a/gcloud/contrib/template_market/permission.py b/gcloud/contrib/template_market/permission.py index b7720f852f..bcd3e4ba41 100644 --- a/gcloud/contrib/template_market/permission.py +++ b/gcloud/contrib/template_market/permission.py @@ -42,12 +42,13 @@ def has_permission(self, request, view): class SharedProcessTemplatePermission(permissions.BasePermission): def has_permission(self, request, view): - username = request.user.username - serializer = view.serializer_class(data=request.data) - serializer.is_valid(raise_exception=True) - - template_id_list = [template.get("id") for template in serializer.validated_data["templates"]] if view.action in ["create", "partial_update"]: + username = request.user.username + serializer = view.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + + template_id_list = serializer.validated_data["templates"] + iam_multi_resource_auth_or_raise( username, IAMMeta.FLOW_EDIT_ACTION, template_id_list, "resources_list_for_flows" ) diff --git a/gcloud/contrib/template_market/serializers.py b/gcloud/contrib/template_market/serializers.py index baa8875001..3d030e99fc 100644 --- a/gcloud/contrib/template_market/serializers.py +++ b/gcloud/contrib/template_market/serializers.py @@ -11,7 +11,6 @@ specific language governing permissions and limitations under the License. """ import json -import logging from rest_framework import serializers from gcloud.constants import DATETIME_FORMAT @@ -34,11 +33,10 @@ class TemplateProjectBaseSerializer(serializers.Serializer): class TemplateSharedRecordSerializer(serializers.ModelSerializer): project_id = serializers.CharField(required=True, max_length=32, help_text="项目id") templates = serializers.ListField(required=True, help_text="关联的模板列表") - creator = serializers.CharField(required=False, max_length=32, help_text="创建者") + creator = serializers.CharField(required=True, max_length=32, help_text="创建者") create_at = serializers.DateTimeField(required=False, help_text="创建时间", format=DATETIME_FORMAT) update_at = serializers.DateTimeField(required=False, help_text="更新时间", format=DATETIME_FORMAT) extra_info = serializers.JSONField(required=False, allow_null=True, help_text="额外信息") - id = serializers.IntegerField(required=False, help_text="共享实例id") name = serializers.CharField(required=True, help_text="共享名称") code = serializers.CharField(required=True, help_text="共享标识") category = serializers.CharField(required=True, help_text="共享分类") @@ -56,41 +54,23 @@ class Meta: "create_at", "update_at", "extra_info", - "labels", - "usage_content", - "id", "name", "code", "category", "risk_level", "usage_id", + "labels", + "usage_content", ] - def convert_templates(self, templates): - return [template.get("id") for template in templates] - def create(self, validated_data): - try: - validated_data["templates"] = self.convert_templates(validated_data["templates"]) - return TemplateSharedRecord.objects.create( - scene_shared_id=validated_data["id"], - project_id=validated_data["project_id"], - templates=validated_data["templates"], - creator=validated_data["creator"], - extra_info=validated_data["extra_info"], - ) - except Exception: - logging.exception("Failed to create model sharing record") - raise Exception("Failed to create model sharing record") + fields_to_remove = ["name", "code", "category", "risk_level", "usage_id", "labels", "usage_content"] + for field in fields_to_remove: + validated_data.pop(field, None) + return super().create(validated_data) def update(self, instance, validated_data): - try: - validated_data["templates"] = self.convert_templates(validated_data["templates"]) - instance.project_id = validated_data["project_id"] - instance.templates = validated_data["templates"] - instance.creator = validated_data["creator"] - instance.extra_info = validated_data["extra_info"] - instance.save() - except Exception: - logging.exception("Failed to update model sharing record") - raise Exception("Failed to update model sharing record") + fields_to_remove = ["name", "code", "category", "risk_level", "usage_id", "labels", "usage_content"] + for field in fields_to_remove: + validated_data.pop(field, None) + return super().update(instance, validated_data) diff --git a/gcloud/contrib/template_market/viewsets.py b/gcloud/contrib/template_market/viewsets.py index e35d2f3d02..bd0f790cd4 100644 --- a/gcloud/contrib/template_market/viewsets.py +++ b/gcloud/contrib/template_market/viewsets.py @@ -58,8 +58,9 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.market_client = MarketAPIClient() - def _build_template_data(self, serializer): - + def _build_template_data(self, serializer, **kwargs): + templates = TaskTemplate.objects.filter(id__in=serializer.validated_data["templates"], is_deleted=False) + template_id_list = [{"id": template.id, "name": template.name} for template in templates] data = { "name": serializer.validated_data["name"], "code": serializer.validated_data["code"], @@ -69,17 +70,15 @@ def _build_template_data(self, serializer): "labels": serializer.validated_data["labels"], "source_system": "bk_sops", "project_code": serializer.validated_data["project_id"], - "templates": json.dumps(serializer.validated_data["templates"]), + "templates": json.dumps(template_id_list), "usage_content": serializer.validated_data["usage_content"], } - scene_shared_id = serializer.validated_data.get("id") + scene_shared_id = kwargs.get("scene_shared_id") if scene_shared_id: data["id"] = scene_shared_id return data - def _get_processes_count(self, templates): - template_id_list = [template.get("id") for template in templates] - + def _get_processes_count(self, template_id_list): template_objs = TaskTemplate.objects.filter(id__in=template_id_list) total_count = 0 @@ -126,8 +125,8 @@ def create(self, request, *args, **kwargs): "code": err_code.OPERATION_FAIL.code, } ) - serializer.validated_data["id"] = response_data["data"]["id"] - serializer.create(serializer.validated_data) + serializer.validated_data["scene_shared_id"] = response_data["data"]["id"] + serializer.save() return Response( { "result": True, @@ -142,12 +141,12 @@ def create(self, request, *args, **kwargs): @swagger_auto_schema(request_body=TemplateSharedRecordSerializer) def partial_update(self, request, *args, **kwargs): + scene_shared_id = kwargs["pk"] + instance = self.queryset.get(scene_shared_id=scene_shared_id) serializer = self.serializer_class(data=request.data, partial=True) serializer.is_valid(raise_exception=True) - scene_shared_id = serializer.validated_data.get("id") - - data = self._build_template_data(serializer) + data = self._build_template_data(serializer, scene_shared_id=scene_shared_id) try: response_data = self.market_client.patch_template(data, scene_shared_id) if not response_data.get("result"): @@ -158,8 +157,7 @@ def partial_update(self, request, *args, **kwargs): "code": err_code.OPERATION_FAIL.code, } ) - instance = self.queryset.get(scene_shared_id=scene_shared_id) - serializer.update(instance, serializer.validated_data) + serializer.update(instance, validated_data=serializer.validated_data) return Response( { "result": True, From 7ad80c02f0f135fa527394560d5293af7e3683a6 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Thu, 12 Dec 2024 17:30:22 +0800 Subject: [PATCH 10/14] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E4=BB=A3=E7=A0=81=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/default.py | 2 - env.py | 2 - .../commands/data/api-resources.yml | 6 +- .../views/copy_template_across_project.py | 2 +- gcloud/contrib/template_market/admin.py | 4 +- .../template_market/{utils.py => clients.py} | 10 +- .../migrations/0001_initial.py | 6 +- gcloud/contrib/template_market/models.py | 2 +- gcloud/contrib/template_market/permission.py | 33 ++++--- gcloud/contrib/template_market/serializers.py | 5 - gcloud/contrib/template_market/viewsets.py | 92 +++++-------------- 11 files changed, 58 insertions(+), 106 deletions(-) rename gcloud/contrib/template_market/{utils.py => clients.py} (80%) diff --git a/config/default.py b/config/default.py index c771f2f3f2..be7bbd0adb 100644 --- a/config/default.py +++ b/config/default.py @@ -890,5 +890,3 @@ def check_engine_admin_permission(request, *args, **kwargs): ENABLE_TEMPLATE_MARKET = env.ENABLE_TEMPLATE_MARKET # 流程商店 API 地址 TEMPLATE_MARKET_API_URL = env.TEMPLATE_MARKET_API_URL -# 共享流程最大数量 -MAX_NUMBER_SHARED_PROCESSES = env.MAX_NUMBER_SHARED_PROCESSES diff --git a/env.py b/env.py index cab2ee11d1..2a4e1546ed 100644 --- a/env.py +++ b/env.py @@ -158,5 +158,3 @@ ENABLE_TEMPLATE_MARKET = False if os.getenv("ENABLE_TEMPLATE_MARKET") is None else True # 流程商店 API 地址 TEMPLATE_MARKET_API_URL = os.getenv("TEMPLATE_MARKET_API_URL", "") -# 共享流程最大数量 -MAX_NUMBER_SHARED_PROCESSES = int(os.getenv("MAX_NUMBER_SHARED_PROCESSES", 10)) diff --git a/gcloud/apigw/management/commands/data/api-resources.yml b/gcloud/apigw/management/commands/data/api-resources.yml index cdabd38921..b5712b7c8e 100644 --- a/gcloud/apigw/management/commands/data/api-resources.yml +++ b/gcloud/apigw/management/commands/data/api-resources.yml @@ -762,13 +762,13 @@ paths: operationId: copy_template_across_project description: 跨业务复制模板 tags: - - 通用接口 + - 限制接口 responses: default: description: '' x-bk-apigateway-resource: - isPublic: false - allowApplyPermission: true + isPublic: true + allowApplyPermission: false matchSubpath: false backend: type: HTTP diff --git a/gcloud/apigw/views/copy_template_across_project.py b/gcloud/apigw/views/copy_template_across_project.py index d7673992cc..40366ae9ff 100644 --- a/gcloud/apigw/views/copy_template_across_project.py +++ b/gcloud/apigw/views/copy_template_across_project.py @@ -60,7 +60,7 @@ def copy_template_across_project(request, project_id): operator=request.user.username, ) except Exception as e: - logger.exception("The process fails to be replicated across services: {}".format(e)) + logger.exception("The template fails to be copied across project: {}".format(e)) return { "result": False, "message": "invalid flow data or error occur, please contact administrator", diff --git a/gcloud/contrib/template_market/admin.py b/gcloud/contrib/template_market/admin.py index 9083b78bdd..6b5429b3d9 100644 --- a/gcloud/contrib/template_market/admin.py +++ b/gcloud/contrib/template_market/admin.py @@ -18,6 +18,6 @@ @admin.register(models.TemplateSharedRecord) class TemplateSharedRecordAdmin(admin.ModelAdmin): - list_display = ["scene_shared_id", "project_id", "templates", "creator", "create_at", "update_at", "extra_info"] + list_display = ["market_record_id", "project_id", "templates", "creator", "create_at", "update_at", "extra_info"] list_filter = ["project_id", "creator", "create_at", "update_at"] - search_fields = ["scene_shared_id", "project_id", "creator"] + search_fields = ["market_record_id", "project_id", "creator"] diff --git a/gcloud/contrib/template_market/utils.py b/gcloud/contrib/template_market/clients.py similarity index 80% rename from gcloud/contrib/template_market/utils.py rename to gcloud/contrib/template_market/clients.py index 359b333fdd..97cafd2da2 100644 --- a/gcloud/contrib/template_market/utils.py +++ b/gcloud/contrib/template_market/clients.py @@ -23,17 +23,17 @@ def __init__(self): def _get_url(self, endpoint): return f"{self.base_url}{endpoint}" - def get_template_list(self): - url = self._get_url("/sre_scene/flow_template_scene/") + def get_market_template_list(self): + url = self._get_url("/sre_scene/flow_template_scene/?is_all=true") response = requests.get(url) return response.json() - def create_template(self, data): + def create_market_template_record(self, data): url = self._get_url("/sre_scene/flow_template_scene/") response = requests.post(url, json=data) return response.json() - def patch_template(self, data, scene_shared_id): - url = self._get_url(f"/sre_scene/flow_template_scene/{scene_shared_id}/") + def patch_market_template_record(self, data, market_record_id): + url = self._get_url(f"/sre_scene/flow_template_scene/{market_record_id}/") response = requests.patch(url, json=data) return response.json() diff --git a/gcloud/contrib/template_market/migrations/0001_initial.py b/gcloud/contrib/template_market/migrations/0001_initial.py index 50b0871bde..b4b5c07fda 100644 --- a/gcloud/contrib/template_market/migrations/0001_initial.py +++ b/gcloud/contrib/template_market/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.15 on 2024-12-11 13:46 +# Generated by Django 3.2.15 on 2024-12-12 09:15 from django.db import migrations, models @@ -14,8 +14,8 @@ class Migration(migrations.Migration): name="TemplateSharedRecord", fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("scene_shared_id", models.IntegerField(help_text="共享实例id", verbose_name="共享实例id")), - ("project_id", models.IntegerField(default=-1, help_text="项目 ID", verbose_name="项目 ID")), + ("market_record_id", models.CharField(db_index=True, max_length=32, verbose_name="模板市场记录 ID")), + ("project_id", models.IntegerField(help_text="项目 ID", verbose_name="项目 ID")), ("templates", models.JSONField(help_text="模板 ID 列表", verbose_name="模板 ID 列表")), ("creator", models.CharField(default="", max_length=32, verbose_name="创建者")), ("create_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")), diff --git a/gcloud/contrib/template_market/models.py b/gcloud/contrib/template_market/models.py index f16eaf7ab3..31b4e6f841 100644 --- a/gcloud/contrib/template_market/models.py +++ b/gcloud/contrib/template_market/models.py @@ -16,7 +16,7 @@ class TemplateSharedRecord(models.Model): - scene_shared_id = models.IntegerField(_("共享实例id"), help_text="共享实例id") + market_record_id = models.CharField(_("共享实例 ID"), max_length=32, help_text="共享实例 ID", db_index=True) project_id = models.IntegerField(_("项目 ID"), default=-1, help_text="项目 ID") templates = models.JSONField(_("模板 ID 列表"), help_text="模板 ID 列表") creator = models.CharField(_("创建者"), max_length=32, default="") diff --git a/gcloud/contrib/template_market/permission.py b/gcloud/contrib/template_market/permission.py index bcd3e4ba41..e147391967 100644 --- a/gcloud/contrib/template_market/permission.py +++ b/gcloud/contrib/template_market/permission.py @@ -12,27 +12,24 @@ """ import logging -from django.db.models import Q +from iam.exceptions import MultiAuthFailedException from rest_framework import permissions from gcloud.conf import settings from gcloud.contrib.template_market.models import TemplateSharedRecord +from gcloud.contrib.template_market.serializers import TemplateProjectBaseSerializer from gcloud.iam_auth import IAMMeta from gcloud.iam_auth.utils import iam_multi_resource_auth_or_raise class TemplatePreviewPermission(permissions.BasePermission): def has_permission(self, request, view): - try: - template_id = int(request.GET.get("template_id")) - project_id = int(request.GET.get("project_id")) - except (TypeError, ValueError): - logging.warning("Missing or invalid required parameters.") - return False + serializer = TemplateProjectBaseSerializer(data=request.GET) + serializer.is_valid(raise_exception=True) - record = TemplateSharedRecord.objects.filter( - Q(project_id=project_id) & Q(templates__contains=[template_id]) - ).first() + template_id = int(serializer.validated_data["template_id"]) + project_id = int(serializer.validated_data["project_id"]) + record = TemplateSharedRecord.objects.filter(project_id=project_id, templates__contains=[template_id]).first() if record is None: logging.warning("The specified template could not be found") return False @@ -42,15 +39,21 @@ def has_permission(self, request, view): class SharedProcessTemplatePermission(permissions.BasePermission): def has_permission(self, request, view): + if not settings.ENABLE_TEMPLATE_MARKET: + return False + if view.action in ["create", "partial_update"]: username = request.user.username serializer = view.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) template_id_list = serializer.validated_data["templates"] + try: + iam_multi_resource_auth_or_raise( + username, IAMMeta.FLOW_EDIT_ACTION, template_id_list, "resources_list_for_flows" + ) + except MultiAuthFailedException: + logging.exception("You do not have permission to perform this operation") + return False - iam_multi_resource_auth_or_raise( - username, IAMMeta.FLOW_EDIT_ACTION, template_id_list, "resources_list_for_flows" - ) - - return settings.ENABLE_TEMPLATE_MARKET + return True diff --git a/gcloud/contrib/template_market/serializers.py b/gcloud/contrib/template_market/serializers.py index 3d030e99fc..82248119f6 100644 --- a/gcloud/contrib/template_market/serializers.py +++ b/gcloud/contrib/template_market/serializers.py @@ -13,7 +13,6 @@ import json from rest_framework import serializers -from gcloud.constants import DATETIME_FORMAT from gcloud.contrib.template_market.models import TemplateSharedRecord @@ -34,8 +33,6 @@ class TemplateSharedRecordSerializer(serializers.ModelSerializer): project_id = serializers.CharField(required=True, max_length=32, help_text="项目id") templates = serializers.ListField(required=True, help_text="关联的模板列表") creator = serializers.CharField(required=True, max_length=32, help_text="创建者") - create_at = serializers.DateTimeField(required=False, help_text="创建时间", format=DATETIME_FORMAT) - update_at = serializers.DateTimeField(required=False, help_text="更新时间", format=DATETIME_FORMAT) extra_info = serializers.JSONField(required=False, allow_null=True, help_text="额外信息") name = serializers.CharField(required=True, help_text="共享名称") code = serializers.CharField(required=True, help_text="共享标识") @@ -51,8 +48,6 @@ class Meta: "project_id", "templates", "creator", - "create_at", - "update_at", "extra_info", "name", "code", diff --git a/gcloud/contrib/template_market/viewsets.py b/gcloud/contrib/template_market/viewsets.py index bd0f790cd4..ba670d2ccd 100644 --- a/gcloud/contrib/template_market/viewsets.py +++ b/gcloud/contrib/template_market/viewsets.py @@ -18,7 +18,6 @@ from rest_framework import permissions from gcloud import err_code -from gcloud.conf import settings from drf_yasg.utils import swagger_auto_schema from gcloud.contrib.template_market.serializers import ( TemplateSharedRecordSerializer, @@ -27,7 +26,7 @@ ) from gcloud.contrib.template_market.models import TemplateSharedRecord from gcloud.taskflow3.models import TaskTemplate -from gcloud.contrib.template_market.utils import MarketAPIClient +from gcloud.contrib.template_market.clients import MarketAPIClient from gcloud.contrib.template_market.permission import TemplatePreviewPermission, SharedProcessTemplatePermission @@ -78,25 +77,18 @@ def _build_template_data(self, serializer, **kwargs): data["id"] = scene_shared_id return data - def _get_processes_count(self, template_id_list): - template_objs = TaskTemplate.objects.filter(id__in=template_id_list) - - total_count = 0 - for template in template_objs: - activities = template.pipeline_tree.get("activities", []) - total_count += len(activities) - - return total_count - def list(self, request, *args, **kwargs): - response_data = self.market_client.get_template_list() + response_data = self.market_client.get_market_template_list() if not response_data["result"]: - logging.exception(f"Get template information from market failed, error code: {response_data.get('code')}") + logging.exception("Failed to obtain the market template list") return Response( - {"result": False, "message": "Get template information failed", "code": err_code.OPERATION_FAIL.code} + { + "result": False, + "message": "Failed to obtain the market template list", + "code": err_code.OPERATION_FAIL.code, + } ) - return Response({"result": True, "data": response_data, "code": err_code.SUCCESS.code}) @swagger_auto_schema(request_body=TemplateSharedRecordSerializer) @@ -104,70 +96,36 @@ def create(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) - template_count = self._get_processes_count(serializer.validated_data["templates"]) - if template_count > settings.MAX_NUMBER_SHARED_PROCESSES: + data = self._build_template_data(serializer) + response_data = self.market_client.create_market_template_record(data) + if not response_data.get("result"): return Response( { "result": False, - "message": "The number of selected templates exceeds the limit", + "message": "Failed to create market template record", "code": err_code.OPERATION_FAIL.code, } ) - - data = self._build_template_data(serializer) - try: - response_data = self.market_client.create_template(data) - if not response_data.get("result"): - return Response( - { - "result": False, - "message": "Failed to share template to sre store", - "code": err_code.OPERATION_FAIL.code, - } - ) - serializer.validated_data["scene_shared_id"] = response_data["data"]["id"] - serializer.save() - return Response( - { - "result": True, - "data": "response_data", - "message": "Share template successfully", - "code": err_code.SUCCESS.code, - } - ) - except Exception as e: - logging.exception("Share template failed: %s", e) - return Response({"result": False, "message": "Share template failed", "code": err_code.OPERATION_FAIL.code}) + serializer.validated_data["market_record_id"] = response_data["data"]["id"] + serializer.create(serializer.validated_data) + return Response({"result": True, "data": response_data, "code": err_code.SUCCESS.code}) @swagger_auto_schema(request_body=TemplateSharedRecordSerializer) def partial_update(self, request, *args, **kwargs): - scene_shared_id = kwargs["pk"] - instance = self.queryset.get(scene_shared_id=scene_shared_id) + market_record_id = kwargs["pk"] + instance = self.queryset.get(market_record_id=market_record_id) serializer = self.serializer_class(data=request.data, partial=True) serializer.is_valid(raise_exception=True) - data = self._build_template_data(serializer, scene_shared_id=scene_shared_id) - try: - response_data = self.market_client.patch_template(data, scene_shared_id) - if not response_data.get("result"): - return Response( - { - "result": False, - "message": "Update template to sre store failed", - "code": err_code.OPERATION_FAIL.code, - } - ) - serializer.update(instance, validated_data=serializer.validated_data) + data = self._build_template_data(serializer, market_record_id=market_record_id) + response_data = self.market_client.patch_market_template_record(data, market_record_id) + if not response_data.get("result"): return Response( { - "result": True, - "data": response_data, - "message": "Share template successfully", - "code": err_code.SUCCESS.code, + "result": False, + "message": "Failed to update market template record", + "code": err_code.OPERATION_FAIL.code, } ) - except Exception as e: - logging.exception("Failed to update scene template: %s", e) - return Response( - {"result": False, "message": "Failed to update scene template", "code": err_code.OPERATION_FAIL.code} - ) + serializer.update(instance=instance, validated_data=serializer.validated_data) + return Response({"result": True, "data": response_data, "code": err_code.SUCCESS.code}) From 9b7ca600fbe80ab8bdee7f9b0245e22dfccd5404 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Thu, 12 Dec 2024 17:33:18 +0800 Subject: [PATCH 11/14] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E9=81=97?= =?UTF-8?q?=E6=BC=8F=E9=97=AE=E9=A2=98=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gcloud/apigw/management/commands/data/api-resources.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcloud/apigw/management/commands/data/api-resources.yml b/gcloud/apigw/management/commands/data/api-resources.yml index b5712b7c8e..0f634b08de 100644 --- a/gcloud/apigw/management/commands/data/api-resources.yml +++ b/gcloud/apigw/management/commands/data/api-resources.yml @@ -767,7 +767,7 @@ paths: default: description: '' x-bk-apigateway-resource: - isPublic: true + isPublic: false allowApplyPermission: false matchSubpath: false backend: From e90f0f47afcfb4a357878f88e50344c85d54aa47 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Fri, 13 Dec 2024 08:38:08 +0800 Subject: [PATCH 12/14] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=85=B3=E7=B3=BB=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/copy_template_across_project.py | 14 ++++- gcloud/contrib/template_market/admin.py | 6 +- .../migrations/0001_initial.py | 7 +-- gcloud/contrib/template_market/models.py | 3 +- gcloud/contrib/template_market/permission.py | 8 +-- gcloud/contrib/template_market/serializers.py | 61 +++++++++++-------- gcloud/contrib/template_market/urls.py | 4 +- gcloud/contrib/template_market/viewsets.py | 41 +++++++------ 8 files changed, 83 insertions(+), 61 deletions(-) diff --git a/gcloud/apigw/views/copy_template_across_project.py b/gcloud/apigw/views/copy_template_across_project.py index 40366ae9ff..ad8f411e90 100644 --- a/gcloud/apigw/views/copy_template_across_project.py +++ b/gcloud/apigw/views/copy_template_across_project.py @@ -10,8 +10,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. """ - -import ujson as json +import logging +import json from apigw_manager.apigw.decorators import apigw_require from blueapps.account.decorators import login_exempt from django.views.decorators.csrf import csrf_exempt @@ -25,6 +25,7 @@ ) from gcloud.apigw.views.utils import logger +from gcloud.contrib.template_market.models import TemplateSharedRecord from gcloud.tasktmpl3.models import TaskTemplate from gcloud.template_base.utils import format_import_result_to_response_data from gcloud.utils.decorators import request_validate @@ -51,6 +52,15 @@ def copy_template_across_project(request, project_id): new_project_id = params_data["new_project_id"] template_id = params_data["template_id"] + record = TemplateSharedRecord.objects.filter(project_id=request.project.id, template_id=template_id).first() + if record is None: + logging.warning("The specified template could not be found") + return { + "result": False, + "message": "The specified template could not be found", + "code": err_code.REQUEST_FORBIDDEN_INVALID.code, + } + try: export_data = TaskTemplate.objects.export_templates([template_id], is_full=False, project_id=request.project.id) import_result = TaskTemplate.objects.import_templates( diff --git a/gcloud/contrib/template_market/admin.py b/gcloud/contrib/template_market/admin.py index 6b5429b3d9..8c415c5404 100644 --- a/gcloud/contrib/template_market/admin.py +++ b/gcloud/contrib/template_market/admin.py @@ -18,6 +18,6 @@ @admin.register(models.TemplateSharedRecord) class TemplateSharedRecordAdmin(admin.ModelAdmin): - list_display = ["market_record_id", "project_id", "templates", "creator", "create_at", "update_at", "extra_info"] - list_filter = ["project_id", "creator", "create_at", "update_at"] - search_fields = ["market_record_id", "project_id", "creator"] + list_display = ["project_id", "template_id", "creator", "create_at", "update_at", "extra_info"] + list_filter = ["project_id", "template_id", "creator", "create_at", "update_at"] + search_fields = ["project_id", "creator"] diff --git a/gcloud/contrib/template_market/migrations/0001_initial.py b/gcloud/contrib/template_market/migrations/0001_initial.py index b4b5c07fda..1a2ab4fc8a 100644 --- a/gcloud/contrib/template_market/migrations/0001_initial.py +++ b/gcloud/contrib/template_market/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.15 on 2024-12-12 09:15 +# Generated by Django 3.2.15 on 2024-12-12 12:23 from django.db import migrations, models @@ -14,9 +14,8 @@ class Migration(migrations.Migration): name="TemplateSharedRecord", fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("market_record_id", models.CharField(db_index=True, max_length=32, verbose_name="模板市场记录 ID")), - ("project_id", models.IntegerField(help_text="项目 ID", verbose_name="项目 ID")), - ("templates", models.JSONField(help_text="模板 ID 列表", verbose_name="模板 ID 列表")), + ("project_id", models.IntegerField(default=-1, help_text="项目 ID", verbose_name="项目 ID")), + ("template_id", models.JSONField(db_index=True, help_text="模板 ID 列表", verbose_name="模板 ID 列表")), ("creator", models.CharField(default="", max_length=32, verbose_name="创建者")), ("create_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")), ("update_at", models.DateTimeField(auto_now=True, verbose_name="更新时间")), diff --git a/gcloud/contrib/template_market/models.py b/gcloud/contrib/template_market/models.py index 31b4e6f841..1adc82ce1f 100644 --- a/gcloud/contrib/template_market/models.py +++ b/gcloud/contrib/template_market/models.py @@ -16,9 +16,8 @@ class TemplateSharedRecord(models.Model): - market_record_id = models.CharField(_("共享实例 ID"), max_length=32, help_text="共享实例 ID", db_index=True) project_id = models.IntegerField(_("项目 ID"), default=-1, help_text="项目 ID") - templates = models.JSONField(_("模板 ID 列表"), help_text="模板 ID 列表") + template_id = models.JSONField(_("模板 ID 列表"), help_text="模板 ID 列表", db_index=True) creator = models.CharField(_("创建者"), max_length=32, default="") create_at = models.DateTimeField(_("创建时间"), auto_now_add=True) update_at = models.DateTimeField(verbose_name=_("更新时间"), auto_now=True) diff --git a/gcloud/contrib/template_market/permission.py b/gcloud/contrib/template_market/permission.py index e147391967..8ac657d348 100644 --- a/gcloud/contrib/template_market/permission.py +++ b/gcloud/contrib/template_market/permission.py @@ -29,7 +29,7 @@ def has_permission(self, request, view): template_id = int(serializer.validated_data["template_id"]) project_id = int(serializer.validated_data["project_id"]) - record = TemplateSharedRecord.objects.filter(project_id=project_id, templates__contains=[template_id]).first() + record = TemplateSharedRecord.objects.filter(project_id=project_id, template_id=template_id).first() if record is None: logging.warning("The specified template could not be found") return False @@ -37,7 +37,7 @@ def has_permission(self, request, view): return True -class SharedProcessTemplatePermission(permissions.BasePermission): +class SharedTemplateRecordPermission(permissions.BasePermission): def has_permission(self, request, view): if not settings.ENABLE_TEMPLATE_MARKET: return False @@ -47,13 +47,13 @@ def has_permission(self, request, view): serializer = view.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) - template_id_list = serializer.validated_data["templates"] + template_id_list = serializer.validated_data["template_ids"] try: iam_multi_resource_auth_or_raise( username, IAMMeta.FLOW_EDIT_ACTION, template_id_list, "resources_list_for_flows" ) except MultiAuthFailedException: - logging.exception("You do not have permission to perform this operation") + logging.exception("Template permission verification failed") return False return True diff --git a/gcloud/contrib/template_market/serializers.py b/gcloud/contrib/template_market/serializers.py index 82248119f6..ebcab55713 100644 --- a/gcloud/contrib/template_market/serializers.py +++ b/gcloud/contrib/template_market/serializers.py @@ -21,6 +21,7 @@ class TemplatePreviewSerializer(serializers.Serializer): pipeline_tree = serializers.SerializerMethodField(read_only=True, help_text="pipeline_tree") def get_pipeline_tree(self, obj): + # todo 节点信息防护 return json.dumps(obj.pipeline_tree) @@ -29,9 +30,9 @@ class TemplateProjectBaseSerializer(serializers.Serializer): project_id = serializers.CharField(required=True, help_text="项目id") -class TemplateSharedRecordSerializer(serializers.ModelSerializer): +class TemplateSharedRecordSerializer(serializers.Serializer): project_id = serializers.CharField(required=True, max_length=32, help_text="项目id") - templates = serializers.ListField(required=True, help_text="关联的模板列表") + template_ids = serializers.ListField(required=True, help_text="关联的模板列表") creator = serializers.CharField(required=True, max_length=32, help_text="创建者") extra_info = serializers.JSONField(required=False, allow_null=True, help_text="额外信息") name = serializers.CharField(required=True, help_text="共享名称") @@ -42,30 +43,36 @@ class TemplateSharedRecordSerializer(serializers.ModelSerializer): labels = serializers.ListField(child=serializers.IntegerField(), required=True, help_text="共享标签列表") usage_content = serializers.JSONField(required=True, help_text="使用说明") - class Meta: - model = TemplateSharedRecord - fields = [ - "project_id", - "templates", - "creator", - "extra_info", - "name", - "code", - "category", - "risk_level", - "usage_id", - "labels", - "usage_content", - ] + def create_shared_record(self, project_id, market_record_id, template_ids, creator): + for template_id in template_ids: + existing_record, created = TemplateSharedRecord.objects.get_or_create( + project_id=project_id, + template_id=template_id, + defaults={"creator": creator, "extra_info": {"market_record_ids": [market_record_id]}}, + ) + if not created: + market_ids = existing_record.extra_info.setdefault("market_record_ids", []) + if market_record_id not in market_ids: + market_ids.append(market_record_id) + existing_record.save() - def create(self, validated_data): - fields_to_remove = ["name", "code", "category", "risk_level", "usage_id", "labels", "usage_content"] - for field in fields_to_remove: - validated_data.pop(field, None) - return super().create(validated_data) + def update_shared_record(self, new_template_ids, market_record_id, project_id, creator): + market_record_id = int(market_record_id) - def update(self, instance, validated_data): - fields_to_remove = ["name", "code", "category", "risk_level", "usage_id", "labels", "usage_content"] - for field in fields_to_remove: - validated_data.pop(field, None) - return super().update(instance, validated_data) + existing_records = TemplateSharedRecord.objects.filter( + project_id=project_id, extra_info__market_record_ids__contains=[market_record_id] + ) + existing_template_ids = set(existing_records.values_list("template_id", flat=True)) + templates_to_remove = existing_template_ids - set(new_template_ids) + + for template_id in templates_to_remove: + current_template_record = existing_records.get(template_id=template_id) + current_market_ids = current_template_record.extra_info.get("market_record_ids", []) + if market_record_id in current_market_ids: + current_market_ids.remove(market_record_id) + current_template_record.extra_info["market_record_ids"] = current_market_ids + current_template_record.save() + + templates_to_add = set(new_template_ids) - existing_template_ids + if templates_to_add: + self.create_shared_record(project_id, market_record_id, list(templates_to_add), creator) diff --git a/gcloud/contrib/template_market/urls.py b/gcloud/contrib/template_market/urls.py index 4b9ddd09bf..250be933f4 100644 --- a/gcloud/contrib/template_market/urls.py +++ b/gcloud/contrib/template_market/urls.py @@ -13,12 +13,12 @@ from django.conf.urls import include, url from rest_framework.routers import DefaultRouter -from gcloud.contrib.template_market.viewsets import TemplatePreviewViewSet, SharedProcessTemplateViewSet +from gcloud.contrib.template_market.viewsets import TemplatePreviewViewSet, SharedTemplateRecordsViewSet template_market_router = DefaultRouter() template_market_router.register(r"template_preview", TemplatePreviewViewSet) -template_market_router.register(r"shared_process_templates", SharedProcessTemplateViewSet) +template_market_router.register(r"shared_templates_records", SharedTemplateRecordsViewSet) urlpatterns = [ url(r"^api/", include(template_market_router.urls)), diff --git a/gcloud/contrib/template_market/viewsets.py b/gcloud/contrib/template_market/viewsets.py index ba670d2ccd..4bbf521322 100644 --- a/gcloud/contrib/template_market/viewsets.py +++ b/gcloud/contrib/template_market/viewsets.py @@ -18,6 +18,7 @@ from rest_framework import permissions from gcloud import err_code +from gcloud.conf import settings from drf_yasg.utils import swagger_auto_schema from gcloud.contrib.template_market.serializers import ( TemplateSharedRecordSerializer, @@ -27,7 +28,7 @@ from gcloud.contrib.template_market.models import TemplateSharedRecord from gcloud.taskflow3.models import TaskTemplate from gcloud.contrib.template_market.clients import MarketAPIClient -from gcloud.contrib.template_market.permission import TemplatePreviewPermission, SharedProcessTemplatePermission +from gcloud.contrib.template_market.permission import TemplatePreviewPermission, SharedTemplateRecordPermission class TemplatePreviewViewSet(viewsets.ViewSet): @@ -48,18 +49,16 @@ def retrieve(self, request, *args, **kwargs): return Response({"result": True, "data": serializer.data, "code": err_code.SUCCESS.code}) -class SharedProcessTemplateViewSet(viewsets.ViewSet): +class SharedTemplateRecordsViewSet(viewsets.ViewSet): queryset = TemplateSharedRecord.objects.all() serializer_class = TemplateSharedRecordSerializer - permission_classes = [permissions.IsAuthenticated, SharedProcessTemplatePermission] + permission_classes = [permissions.IsAuthenticated, SharedTemplateRecordPermission] - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.market_client = MarketAPIClient() + market_client = MarketAPIClient() def _build_template_data(self, serializer, **kwargs): - templates = TaskTemplate.objects.filter(id__in=serializer.validated_data["templates"], is_deleted=False) - template_id_list = [{"id": template.id, "name": template.name} for template in templates] + templates = TaskTemplate.objects.filter(id__in=serializer.validated_data["template_ids"], is_deleted=False) + template_info = [{"id": template.id, "name": template.name} for template in templates] data = { "name": serializer.validated_data["name"], "code": serializer.validated_data["code"], @@ -67,14 +66,14 @@ def _build_template_data(self, serializer, **kwargs): "risk_level": serializer.validated_data["risk_level"], "usage_id": serializer.validated_data["usage_id"], "labels": serializer.validated_data["labels"], - "source_system": "bk_sops", + "source_system": settings.APP_CODE, "project_code": serializer.validated_data["project_id"], - "templates": json.dumps(template_id_list), + "templates": json.dumps(template_info), "usage_content": serializer.validated_data["usage_content"], } - scene_shared_id = kwargs.get("scene_shared_id") - if scene_shared_id: - data["id"] = scene_shared_id + market_record_id = kwargs.get("market_record_id") + if market_record_id: + data["id"] = market_record_id return data def list(self, request, *args, **kwargs): @@ -106,14 +105,17 @@ def create(self, request, *args, **kwargs): "code": err_code.OPERATION_FAIL.code, } ) - serializer.validated_data["market_record_id"] = response_data["data"]["id"] - serializer.create(serializer.validated_data) + serializer.create_shared_record( + project_id=int(serializer.validated_data["project_id"]), + template_ids=serializer.validated_data["template_ids"], + market_record_id=response_data["data"]["id"], + creator=serializer.validated_data["creator"], + ) return Response({"result": True, "data": response_data, "code": err_code.SUCCESS.code}) @swagger_auto_schema(request_body=TemplateSharedRecordSerializer) def partial_update(self, request, *args, **kwargs): market_record_id = kwargs["pk"] - instance = self.queryset.get(market_record_id=market_record_id) serializer = self.serializer_class(data=request.data, partial=True) serializer.is_valid(raise_exception=True) @@ -127,5 +129,10 @@ def partial_update(self, request, *args, **kwargs): "code": err_code.OPERATION_FAIL.code, } ) - serializer.update(instance=instance, validated_data=serializer.validated_data) + serializer.update_shared_record( + project_id=int(serializer.validated_data["project_id"]), + new_template_ids=serializer.validated_data["template_ids"], + market_record_id=market_record_id, + creator=serializer.validated_data["creator"], + ) return Response({"result": True, "data": response_data, "code": err_code.SUCCESS.code}) From faa50e396ff21a75767ef52f61b8bcd607e73202 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Fri, 13 Dec 2024 12:54:52 +0800 Subject: [PATCH 13/14] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E6=96=B9=E6=B3=95=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gcloud/contrib/template_market/clients.py | 6 +++ gcloud/contrib/template_market/models.py | 50 +++++++++++++++++++ gcloud/contrib/template_market/permission.py | 2 +- gcloud/contrib/template_market/serializers.py | 37 +------------- gcloud/contrib/template_market/urls.py | 4 +- gcloud/contrib/template_market/viewsets.py | 17 ++++--- 6 files changed, 70 insertions(+), 46 deletions(-) diff --git a/gcloud/contrib/template_market/clients.py b/gcloud/contrib/template_market/clients.py index 97cafd2da2..f1da852d9c 100644 --- a/gcloud/contrib/template_market/clients.py +++ b/gcloud/contrib/template_market/clients.py @@ -23,6 +23,12 @@ def __init__(self): def _get_url(self, endpoint): return f"{self.base_url}{endpoint}" + def get_template_detail(self, market_record_id): + url = self._get_url(f"/sre_scene/flow_template_scene/{market_record_id}/") + cookies = {"bk_ticket": "5IT20mfD9mI_uTitTrUuDKTp9GIif7ZaBivi9E6k5qw"} + response = requests.get(url, cookies=cookies) + return response.json() + def get_market_template_list(self): url = self._get_url("/sre_scene/flow_template_scene/?is_all=true") response = requests.get(url) diff --git a/gcloud/contrib/template_market/models.py b/gcloud/contrib/template_market/models.py index 1adc82ce1f..04d02eae07 100644 --- a/gcloud/contrib/template_market/models.py +++ b/gcloud/contrib/template_market/models.py @@ -14,6 +14,54 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from gcloud import err_code + + +class TemplateSharedManager(models.Manager): + def update_shared_record(self, new_template_ids, market_record_id, project_id, creator, existing_template_ids=None): + market_record_id = int(market_record_id) + + if existing_template_ids: + templates_to_remove = existing_template_ids - set(new_template_ids) + if templates_to_remove: + for template_id in templates_to_remove: + current_template_record = TemplateSharedRecord.objects.get(template_id=template_id) + current_market_ids = current_template_record.extra_info.get("market_record_ids", []) + if market_record_id in current_market_ids: + current_market_ids.remove(market_record_id) + current_template_record.extra_info["market_record_ids"] = current_market_ids + current_template_record.save() + if not current_template_record.extra_info["market_record_ids"]: + current_template_record.delete() + else: + return { + "result": False, + "message": "template {} is not in record {}".format(template_id, market_record_id), + "code": err_code.REQUEST_PARAM_INVALID.code, + } + + templates_to_add = set(new_template_ids) - existing_template_ids + if templates_to_add: + new_template_ids = list(templates_to_add) + + new_records = [] + for template_id in new_template_ids: + existing_record, created = TemplateSharedRecord.objects.get_or_create( + project_id=project_id, + template_id=template_id, + defaults={"creator": creator, "extra_info": {"market_record_ids": [market_record_id]}}, + ) + if not created: + market_ids = existing_record.extra_info.setdefault("market_record_ids", []) + if market_record_id not in market_ids: + market_ids.append(market_record_id) + new_records.append(existing_record) + + if new_records: + TemplateSharedRecord.objects.bulk_update(new_records, ["extra_info"]) + + return {"result": True, "message": "update shared record successfully", "code": err_code.SUCCESS.code} + class TemplateSharedRecord(models.Model): project_id = models.IntegerField(_("项目 ID"), default=-1, help_text="项目 ID") @@ -23,6 +71,8 @@ class TemplateSharedRecord(models.Model): update_at = models.DateTimeField(verbose_name=_("更新时间"), auto_now=True) extra_info = models.JSONField(_("额外信息"), blank=True, null=True) + objects = TemplateSharedManager() + class Meta: verbose_name = _("模板共享记录 TemplateSharedRecord") verbose_name_plural = _("模板共享记录 TemplateSharedRecord") diff --git a/gcloud/contrib/template_market/permission.py b/gcloud/contrib/template_market/permission.py index 8ac657d348..d2f6847012 100644 --- a/gcloud/contrib/template_market/permission.py +++ b/gcloud/contrib/template_market/permission.py @@ -24,7 +24,7 @@ class TemplatePreviewPermission(permissions.BasePermission): def has_permission(self, request, view): - serializer = TemplateProjectBaseSerializer(data=request.GET) + serializer = TemplateProjectBaseSerializer(data=request.query_params) serializer.is_valid(raise_exception=True) template_id = int(serializer.validated_data["template_id"]) diff --git a/gcloud/contrib/template_market/serializers.py b/gcloud/contrib/template_market/serializers.py index ebcab55713..89be644f10 100644 --- a/gcloud/contrib/template_market/serializers.py +++ b/gcloud/contrib/template_market/serializers.py @@ -11,9 +11,8 @@ specific language governing permissions and limitations under the License. """ import json -from rest_framework import serializers -from gcloud.contrib.template_market.models import TemplateSharedRecord +from rest_framework import serializers class TemplatePreviewSerializer(serializers.Serializer): @@ -42,37 +41,3 @@ class TemplateSharedRecordSerializer(serializers.Serializer): usage_id = serializers.IntegerField(required=True, help_text="使用说明id") labels = serializers.ListField(child=serializers.IntegerField(), required=True, help_text="共享标签列表") usage_content = serializers.JSONField(required=True, help_text="使用说明") - - def create_shared_record(self, project_id, market_record_id, template_ids, creator): - for template_id in template_ids: - existing_record, created = TemplateSharedRecord.objects.get_or_create( - project_id=project_id, - template_id=template_id, - defaults={"creator": creator, "extra_info": {"market_record_ids": [market_record_id]}}, - ) - if not created: - market_ids = existing_record.extra_info.setdefault("market_record_ids", []) - if market_record_id not in market_ids: - market_ids.append(market_record_id) - existing_record.save() - - def update_shared_record(self, new_template_ids, market_record_id, project_id, creator): - market_record_id = int(market_record_id) - - existing_records = TemplateSharedRecord.objects.filter( - project_id=project_id, extra_info__market_record_ids__contains=[market_record_id] - ) - existing_template_ids = set(existing_records.values_list("template_id", flat=True)) - templates_to_remove = existing_template_ids - set(new_template_ids) - - for template_id in templates_to_remove: - current_template_record = existing_records.get(template_id=template_id) - current_market_ids = current_template_record.extra_info.get("market_record_ids", []) - if market_record_id in current_market_ids: - current_market_ids.remove(market_record_id) - current_template_record.extra_info["market_record_ids"] = current_market_ids - current_template_record.save() - - templates_to_add = set(new_template_ids) - existing_template_ids - if templates_to_add: - self.create_shared_record(project_id, market_record_id, list(templates_to_add), creator) diff --git a/gcloud/contrib/template_market/urls.py b/gcloud/contrib/template_market/urls.py index 250be933f4..a85a343124 100644 --- a/gcloud/contrib/template_market/urls.py +++ b/gcloud/contrib/template_market/urls.py @@ -13,13 +13,13 @@ from django.conf.urls import include, url from rest_framework.routers import DefaultRouter -from gcloud.contrib.template_market.viewsets import TemplatePreviewViewSet, SharedTemplateRecordsViewSet +from gcloud.contrib.template_market.viewsets import TemplatePreviewAPIView, SharedTemplateRecordsViewSet template_market_router = DefaultRouter() -template_market_router.register(r"template_preview", TemplatePreviewViewSet) template_market_router.register(r"shared_templates_records", SharedTemplateRecordsViewSet) urlpatterns = [ url(r"^api/", include(template_market_router.urls)), + url(r"^api/template_preview/$", TemplatePreviewAPIView.as_view()), ] diff --git a/gcloud/contrib/template_market/viewsets.py b/gcloud/contrib/template_market/viewsets.py index 4bbf521322..9ec4849e20 100644 --- a/gcloud/contrib/template_market/viewsets.py +++ b/gcloud/contrib/template_market/viewsets.py @@ -14,6 +14,7 @@ import logging from rest_framework import viewsets +from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import permissions @@ -31,13 +32,13 @@ from gcloud.contrib.template_market.permission import TemplatePreviewPermission, SharedTemplateRecordPermission -class TemplatePreviewViewSet(viewsets.ViewSet): +class TemplatePreviewAPIView(APIView): queryset = TaskTemplate.objects.filter(pipeline_template__isnull=False, is_deleted=False) serializer_class = TemplatePreviewSerializer permission_classes = [permissions.IsAuthenticated, TemplatePreviewPermission] - def retrieve(self, request, *args, **kwargs): - request_serializer = TemplateProjectBaseSerializer(data=request.GET) + def get(self, request, *args, **kwargs): + request_serializer = TemplateProjectBaseSerializer(data=request.query_params) request_serializer.is_valid(raise_exception=True) template_id = request_serializer.validated_data["template_id"] @@ -105,9 +106,9 @@ def create(self, request, *args, **kwargs): "code": err_code.OPERATION_FAIL.code, } ) - serializer.create_shared_record( + TemplateSharedRecord.objects.update_shared_record( project_id=int(serializer.validated_data["project_id"]), - template_ids=serializer.validated_data["template_ids"], + new_template_ids=serializer.validated_data["template_ids"], market_record_id=response_data["data"]["id"], creator=serializer.validated_data["creator"], ) @@ -118,7 +119,8 @@ def partial_update(self, request, *args, **kwargs): market_record_id = kwargs["pk"] serializer = self.serializer_class(data=request.data, partial=True) serializer.is_valid(raise_exception=True) - + existing_records = self.market_client.get_template_detail(market_record_id) + existing_template_ids = set([template["id"] for template in json.loads(existing_records["data"]["templates"])]) data = self._build_template_data(serializer, market_record_id=market_record_id) response_data = self.market_client.patch_market_template_record(data, market_record_id) if not response_data.get("result"): @@ -129,10 +131,11 @@ def partial_update(self, request, *args, **kwargs): "code": err_code.OPERATION_FAIL.code, } ) - serializer.update_shared_record( + TemplateSharedRecord.objects.update_shared_record( project_id=int(serializer.validated_data["project_id"]), new_template_ids=serializer.validated_data["template_ids"], market_record_id=market_record_id, creator=serializer.validated_data["creator"], + existing_template_ids=existing_template_ids, ) return Response({"result": True, "data": response_data, "code": err_code.SUCCESS.code}) From 0bd5bd3fffaa33743bf2ef827af02eb3146e5898 Mon Sep 17 00:00:00 2001 From: guohelu <19503896967@163.com> Date: Fri, 13 Dec 2024 14:10:27 +0800 Subject: [PATCH 14/14] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=90=8D=E7=A7=B0=20#7626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gcloud/contrib/template_market/clients.py | 11 +++++------ gcloud/contrib/template_market/viewsets.py | 8 ++++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/gcloud/contrib/template_market/clients.py b/gcloud/contrib/template_market/clients.py index f1da852d9c..8b448e3273 100644 --- a/gcloud/contrib/template_market/clients.py +++ b/gcloud/contrib/template_market/clients.py @@ -23,23 +23,22 @@ def __init__(self): def _get_url(self, endpoint): return f"{self.base_url}{endpoint}" - def get_template_detail(self, market_record_id): + def get_shared_detail(self, market_record_id): url = self._get_url(f"/sre_scene/flow_template_scene/{market_record_id}/") - cookies = {"bk_ticket": "5IT20mfD9mI_uTitTrUuDKTp9GIif7ZaBivi9E6k5qw"} - response = requests.get(url, cookies=cookies) + response = requests.get(url) return response.json() - def get_market_template_list(self): + def get_shared_list(self): url = self._get_url("/sre_scene/flow_template_scene/?is_all=true") response = requests.get(url) return response.json() - def create_market_template_record(self, data): + def create_shared_record(self, data): url = self._get_url("/sre_scene/flow_template_scene/") response = requests.post(url, json=data) return response.json() - def patch_market_template_record(self, data, market_record_id): + def patch_shared_record(self, data, market_record_id): url = self._get_url(f"/sre_scene/flow_template_scene/{market_record_id}/") response = requests.patch(url, json=data) return response.json() diff --git a/gcloud/contrib/template_market/viewsets.py b/gcloud/contrib/template_market/viewsets.py index 9ec4849e20..b536ef5552 100644 --- a/gcloud/contrib/template_market/viewsets.py +++ b/gcloud/contrib/template_market/viewsets.py @@ -78,7 +78,7 @@ def _build_template_data(self, serializer, **kwargs): return data def list(self, request, *args, **kwargs): - response_data = self.market_client.get_market_template_list() + response_data = self.market_client.get_shared_list() if not response_data["result"]: logging.exception("Failed to obtain the market template list") @@ -97,7 +97,7 @@ def create(self, request, *args, **kwargs): serializer.is_valid(raise_exception=True) data = self._build_template_data(serializer) - response_data = self.market_client.create_market_template_record(data) + response_data = self.market_client.create_shared_record(data) if not response_data.get("result"): return Response( { @@ -119,10 +119,10 @@ def partial_update(self, request, *args, **kwargs): market_record_id = kwargs["pk"] serializer = self.serializer_class(data=request.data, partial=True) serializer.is_valid(raise_exception=True) - existing_records = self.market_client.get_template_detail(market_record_id) + existing_records = self.market_client.get_shared_detail(market_record_id) existing_template_ids = set([template["id"] for template in json.loads(existing_records["data"]["templates"])]) data = self._build_template_data(serializer, market_record_id=market_record_id) - response_data = self.market_client.patch_market_template_record(data, market_record_id) + response_data = self.market_client.patch_shared_record(data, market_record_id) if not response_data.get("result"): return Response( {