Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 流程市场共享模板查看 #7626 #7630

7 changes: 7 additions & 0 deletions config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"gcloud.contrib.develop",
"gcloud.contrib.collection",
"gcloud.contrib.operate_record",
"gcloud.contrib.template_market",
"gcloud.apigw",
"gcloud.common_template",
"gcloud.label",
Expand Down Expand Up @@ -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_TEMPLATE_MARKET = env.ENABLE_TEMPLATE_MARKET
# 流程商店 API 地址
TEMPLATE_MARKET_API_URL = env.TEMPLATE_MARKET_API_URL
10 changes: 5 additions & 5 deletions config/urls_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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_market/", include("gcloud.contrib.template_market.urls")),
]

schema_view = get_schema_view(
Expand Down
5 changes: 5 additions & 0 deletions env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_TEMPLATE_MARKET = False if os.getenv("ENABLE_TEMPLATE_MARKET") is None else True
# 流程商店 API 地址
TEMPLATE_MARKET_API_URL = os.getenv("TEMPLATE_MARKET_API_URL", "")
24 changes: 24 additions & 0 deletions gcloud/apigw/management/commands/data/api-resources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: false
allowApplyPermission: false
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: []
2 changes: 2 additions & 0 deletions gcloud/apigw/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_project import copy_template_across_project

urlpatterns = [
url(r"^dispatch_plugin_query/$", dispatch_plugin_query),
Expand Down Expand Up @@ -129,4 +130,5 @@
url(r"^create_clocked_task/(?P<template_id>\d+)/(?P<project_id>\d+)/$", create_clocked_task),
url(r"^get_mini_app_list/(?P<project_id>\d+)/$", get_mini_app_list),
url(r"^get_task_count/(?P<project_id>\d+)/$", get_task_count),
url(r"^copy_template_across_project/(?P<project_id>\d+)/$", copy_template_across_project),
]
29 changes: 29 additions & 0 deletions gcloud/apigw/validators/copy_template_across_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- 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

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, ""
80 changes: 80 additions & 0 deletions gcloud/apigw/views/copy_template_across_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# -*- 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 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.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
from gcloud.apigw.validators.copy_template_across_project import CopyTemplateAcrossProjectValidator


@login_exempt
@csrf_exempt
@require_POST
@apigw_require
@return_json_response
@project_inject
@request_validate(CopyTemplateAcrossProjectValidator)
@mark_request_whether_is_trust
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.",
"code": err_code.REQUEST_FORBIDDEN_INVALID.code,
}

params_data = json.loads(request.body)
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(
template_data=export_data,
override=False,
project_id=new_project_id,
operator=request.user.username,
)
except Exception as 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",
"code": err_code.UNKNOWN_ERROR.code,
}

return format_import_result_to_response_data(import_result)
Empty file.
23 changes: 23 additions & 0 deletions gcloud/contrib/template_market/admin.py
Original file line number Diff line number Diff line change
@@ -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.template_market import models


@admin.register(models.TemplateSharedRecord)
class TemplateSharedRecordAdmin(admin.ModelAdmin):
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"]
6 changes: 6 additions & 0 deletions gcloud/contrib/template_market/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class TemplatemakerConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "gcloud.contrib.template_market"
44 changes: 44 additions & 0 deletions gcloud/contrib/template_market/clients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# -*- 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_shared_detail(self, market_record_id):
url = self._get_url(f"/sre_scene/flow_template_scene/{market_record_id}/")
response = requests.get(url)
return response.json()

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_shared_record(self, data):
url = self._get_url("/sre_scene/flow_template_scene/")
response = requests.post(url, json=data)
return response.json()

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()
29 changes: 29 additions & 0 deletions gcloud/contrib/template_market/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 3.2.15 on 2024-12-12 12:23

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.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="更新时间")),
("extra_info", models.JSONField(blank=True, null=True, verbose_name="额外信息")),
],
options={
"verbose_name": "模板共享记录 TemplateSharedRecord",
"verbose_name_plural": "模板共享记录 TemplateSharedRecord",
},
),
]
Empty file.
78 changes: 78 additions & 0 deletions gcloud/contrib/template_market/models.py
Original file line number Diff line number Diff line change
@@ -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.
"""

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")
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)
extra_info = models.JSONField(_("额外信息"), blank=True, null=True)

objects = TemplateSharedManager()

class Meta:
verbose_name = _("模板共享记录 TemplateSharedRecord")
verbose_name_plural = _("模板共享记录 TemplateSharedRecord")
Loading
Loading