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: custom module order of app deployment #1707

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 3.2.25 on 2024-11-05 10:28

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('audit', '0002_transfer_op'),
]

operations = [
migrations.AlterField(
model_name='adminoperationrecord',
name='operation',
field=models.CharField(choices=[('create', '新建'), ('delete', '删除'), ('modify', '修改'), ('create_app', '创建应用'), ('online_to_market', '发布到应用市场'), ('offline_from_market', '从应用市场下架'), ('modify_market_info', '完善应用市场配置'), ('modify_market_url', '修改应用市场访问地址'), ('modify_basic_info', '修改基本信息'), ('start', '启动'), ('stop', '停止'), ('scale', '扩缩容'), ('enable', '启用'), ('disable', '停用'), ('apply', '申请'), ('renew', '续期'), ('deploy', '部署'), ('offline', '下架'), ('switch', '切换资源方案'), ('modify_user_feature_flag', '修改用户特性'), ('switch_default_cluster', '切换默认集群'), ('bind_cluster', '切换绑定集群'), ('modify_log_config', '日志采集管理'), ('provision_instance', '分配增强服务实例'), ('recycle_resource', '回收增强服务实例')], max_length=32, verbose_name='操作类型'),
),
migrations.AlterField(
model_name='adminoperationrecord',
name='target',
field=models.CharField(choices=[('app', '应用'), ('module', '模块'), ('process', '进程'), ('env_var', '环境变量'), ('addon', '增强服务'), ('cloud_api', '云 API 权限'), ('secret', '密钥'), ('app_domain', '访问地址'), ('app_member', '应用成员'), ('build_config', '构建配置'), ('volume_mount', '挂载卷'), ('service_discovery', '服务发现'), ('domain_resolution', '域名解析'), ('deploy_restriction', '部署限制'), ('exit_ip', '出口 IP'), ('access_control', '用户限制'), ('cluster', '集群'), ('process_spec_plan', '应用资源方案'), ('bkplugin_tag', '插件分类'), ('bkplugin_distributor', '插件使用方'), ('document', '文档'), ('deploy_failure_tips', '部署失败提示'), ('source_type_spec', '代码库配置'), ('shared_cert', '共享证书'), ('addon_plan', '增强服务方案'), ('plat_user', '平台用户'), ('feature_flag', '特性标记'), ('egress_spec', 'Egress 配置'), ('template', '模板'), ('dashboard_template', '仪表盘模板'), ('buildpack', 'Buildpack'), ('slugbuilder', 'Slugbuilder'), ('slugrunner', 'Slugrunner')], max_length=32, verbose_name='操作对象'),
),
migrations.AlterField(
model_name='appoperationrecord',
name='operation',
field=models.CharField(choices=[('create', '新建'), ('delete', '删除'), ('modify', '修改'), ('create_app', '创建应用'), ('online_to_market', '发布到应用市场'), ('offline_from_market', '从应用市场下架'), ('modify_market_info', '完善应用市场配置'), ('modify_market_url', '修改应用市场访问地址'), ('modify_basic_info', '修改基本信息'), ('start', '启动'), ('stop', '停止'), ('scale', '扩缩容'), ('enable', '启用'), ('disable', '停用'), ('apply', '申请'), ('renew', '续期'), ('deploy', '部署'), ('offline', '下架'), ('switch', '切换资源方案'), ('modify_user_feature_flag', '修改用户特性'), ('switch_default_cluster', '切换默认集群'), ('bind_cluster', '切换绑定集群'), ('modify_log_config', '日志采集管理'), ('provision_instance', '分配增强服务实例'), ('recycle_resource', '回收增强服务实例')], max_length=32, verbose_name='操作类型'),
),
migrations.AlterField(
model_name='appoperationrecord',
name='target',
field=models.CharField(choices=[('app', '应用'), ('module', '模块'), ('process', '进程'), ('env_var', '环境变量'), ('addon', '增强服务'), ('cloud_api', '云 API 权限'), ('secret', '密钥'), ('app_domain', '访问地址'), ('app_member', '应用成员'), ('build_config', '构建配置'), ('volume_mount', '挂载卷'), ('service_discovery', '服务发现'), ('domain_resolution', '域名解析'), ('deploy_restriction', '部署限制'), ('exit_ip', '出口 IP'), ('access_control', '用户限制'), ('cluster', '集群'), ('process_spec_plan', '应用资源方案'), ('bkplugin_tag', '插件分类'), ('bkplugin_distributor', '插件使用方'), ('document', '文档'), ('deploy_failure_tips', '部署失败提示'), ('source_type_spec', '代码库配置'), ('shared_cert', '共享证书'), ('addon_plan', '增强服务方案'), ('plat_user', '平台用户'), ('feature_flag', '特性标记'), ('egress_spec', 'Egress 配置'), ('template', '模板'), ('dashboard_template', '仪表盘模板'), ('buildpack', 'Buildpack'), ('slugbuilder', 'Slugbuilder'), ('slugrunner', 'Slugrunner')], max_length=32, verbose_name='操作对象'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by Django 4.2.16 on 2024-11-06 03:11

from django.db import migrations, models
import django.db.models.deletion
import paasng.utils.models


class Migration(migrations.Migration):

dependencies = [
('modules', '0016_auto_20240904_1439'),
('applications', '0012_application_is_ai_agent_app'),
]

operations = [
migrations.CreateModel(
name='ApplicationDeploymentModuleOrderRecord',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('operator', paasng.utils.models.BkUserField(blank=True, db_index=True, max_length=64, null=True, verbose_name='操作人')),
('before_order', models.JSONField(default=dict, verbose_name='排序前顺序')),
('after_order', models.JSONField(default=dict, verbose_name='排序后顺序')),
('created_at', models.DateTimeField(auto_now_add=True)),
('app', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='applications.application', verbose_name='应用')),
],
),
migrations.CreateModel(
name='ApplicationDeploymentModuleOrder',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('module_name', models.CharField(max_length=20, verbose_name='模块名称')),
('order', models.IntegerField(verbose_name='顺序')),
('app', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='applications.application', verbose_name='应用')),
('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='modules.module', verbose_name='模块')),
],
options={
'verbose_name': '模块顺序',
'unique_together': {('app', 'module')},
},
),
]
20 changes: 20 additions & 0 deletions apiserver/paasng/paasng/platform/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from paasng.infras.iam.permissions.resources.application import ApplicationPermission
from paasng.platform.applications.constants import AppFeatureFlag, ApplicationRole, ApplicationType
from paasng.platform.modules.constants import SourceOrigin
from paasng.platform.modules.models.module import Module
from paasng.utils.basic import get_username_by_bkpaas_user_id
from paasng.utils.models import (
BkUserField,
Expand Down Expand Up @@ -602,3 +603,22 @@ def __str__(self):
@property
def code(self):
return self.application.code


class ApplicationDeploymentModuleOrder(models.Model):
app = models.ForeignKey(Application, on_delete=models.CASCADE, verbose_name="应用")
module = models.ForeignKey(Module, on_delete=models.CASCADE, verbose_name="模块")
module_name = models.CharField(verbose_name="模块名称", max_length=20)
order = models.IntegerField(verbose_name="顺序")

class Meta:
unique_together = ("app", "module")
verbose_name = "模块顺序"


class ApplicationDeploymentModuleOrderRecord(models.Model):
operator = BkUserField(verbose_name="操作人")
app = models.ForeignKey(Application, on_delete=models.DO_NOTHING, verbose_name="应用")
before_order = models.JSONField(default=dict, verbose_name="排序前顺序")
after_order = models.JSONField(default=dict, verbose_name="排序后顺序")
created_at = models.DateTimeField(auto_now_add=True)
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
# to the current version of the project delivered to anyone in the future.

from .app import (
ApplicationDeploymentModuleOrderReqSLZ,
ApplicationDeploymentModuleOrderSLZ,
ApplicationEvaluationIssueCountListResultSLZ,
ApplicationEvaluationListQuerySLZ,
ApplicationEvaluationListResultSLZ,
Expand Down Expand Up @@ -107,4 +109,6 @@
"RoleField",
"AppIDUniqueValidator",
"ApplicationMembersInfoSLZ",
"ApplicationDeploymentModuleOrderSLZ",
"ApplicationDeploymentModuleOrderReqSLZ",
]
28 changes: 28 additions & 0 deletions apiserver/paasng/paasng/platform/applications/serializers/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,31 @@ def get_last_operator(self, application: Application):
class Meta:
model = Application
fields = ["id", "code", "name", "administrators", "devopses", "developers", "last_operator"]


class ApplicationDeploymentModuleOrderSLZ(serializers.Serializer):
module_name = serializers.CharField(max_length=20, required=True, help_text="模块名称")
order = serializers.IntegerField(required=True, help_text="模块顺序")


class ApplicationDeploymentModuleOrderReqSLZ(serializers.Serializer):
module_orders = ApplicationDeploymentModuleOrderSLZ(many=True, required=True)

def validate(self, data):
module_names = []
orders = []

# 模块名和顺序不能重复
for module in data["module_orders"]:
module_name = module["module_name"]
order = module["order"]

if module_name in module_names:
raise serializers.ValidationError(f"Duplicate module_name: {module_name}")
if order in orders:
raise serializers.ValidationError(f"Duplicate order: {order}")

module_names.append(module_name)
orders.append(order)

return data
9 changes: 9 additions & 0 deletions apiserver/paasng/paasng/platform/applications/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,15 @@
),
]

# 部署管理-进程列表 Module 顺序
urlpatterns += [
re_path(
r"^api/bkapps/applications/(?P<code>[^/]+)/deployment/module_order/$",
views.ApplicationDeploymentModuleOrderViewSet.as_view({"get": "list", "post": "upsert"}),
name="api.applications.deployment.module_order",
),
]

# Multi-editions specific start

try:
Expand Down
71 changes: 71 additions & 0 deletions apiserver/paasng/paasng/platform/applications/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# to the current version of the project delivered to anyone in the future.

import base64
import json
import logging
import random
import string
Expand Down Expand Up @@ -86,6 +87,8 @@
from paasng.platform.applications.mixins import ApplicationCodeInPathMixin
from paasng.platform.applications.models import (
Application,
ApplicationDeploymentModuleOrder,
ApplicationDeploymentModuleOrderRecord,
ApplicationEnvironment,
JustLeaveAppManager,
UserApplicationFilter,
Expand Down Expand Up @@ -119,6 +122,7 @@
from paasng.platform.mgrlegacy.migrate import get_migration_process_status
from paasng.platform.modules.constants import ExposedURLType, ModuleName, SourceOrigin
from paasng.platform.modules.manager import init_module_in_view
from paasng.platform.modules.models.module import Module
from paasng.platform.modules.protections import ModuleDeletionPreparer
from paasng.platform.scene_app.initializer import SceneAPPInitializer
from paasng.platform.templates.constants import TemplateType
Expand Down Expand Up @@ -1498,3 +1502,70 @@ def create_sys_third_app(self, request, sys_id):
data={"bk_app_code": application.code, "bk_app_secret": secret},
status=status.HTTP_201_CREATED,
)


class ApplicationDeploymentModuleOrderViewSet(viewsets.ViewSet, ApplicationCodeInPathMixin):
"""部署管理-进程列表,模块的排序"""

permission_classes = [IsAuthenticated, application_perm_class(AppAction.BASIC_DEVELOP)]

@swagger_auto_schema(request_body=slzs.ApplicationDeploymentModuleOrderReqSLZ)
def upsert(self, request, code):
"""设置模块的排序"""
serializer = slzs.ApplicationDeploymentModuleOrderReqSLZ(data=request.data)
serializer.is_valid(raise_exception=True)
module_orders_data = serializer.validated_data["module_orders"]

application = self.get_application()
modules = Module.objects.filter(application=application)

# 记录排序操作日志
# 操作前
old_module_orders = list(
ApplicationDeploymentModuleOrder.objects.filter(app=application).values("module_name", "order")
)
old_module_orders_json = json.dumps(old_module_orders)

# 更新或创建模块排序
for module in modules:
for item in module_orders_data:
# 过滤掉模块名称不存在的
if item["module_name"] == module.name:
ApplicationDeploymentModuleOrder.objects.update_or_create(
app=application,
module=module,
module_name=item["module_name"],
defaults={
"order": item["order"],
},
)

# 操作后
new_module_orders = list(
ApplicationDeploymentModuleOrder.objects.filter(app=application).values("module_name", "order")
)
new_module_orders_json = json.dumps(new_module_orders)

if old_module_orders_json == new_module_orders_json:
return Response(status=status.HTTP_204_NO_CONTENT)

record = ApplicationDeploymentModuleOrderRecord(
operator=request.user.pk,
app=application,
before_order=old_module_orders_json,
after_order=new_module_orders_json,
)
record.save()

return Response(status=status.HTTP_201_CREATED)

def list(self, request, code):
"""获取模块的排序"""
application = self.get_application()
data = (
ApplicationDeploymentModuleOrder.objects.filter(app=application)
.order_by("order")
.values("module_name", "order")
)
serializer = slzs.ApplicationDeploymentModuleOrderSLZ(data, many=True)
return Response(serializer.data)
67 changes: 66 additions & 1 deletion apiserver/paasng/tests/api/test_applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from paasng.utils.basic import get_username_by_bkpaas_user_id
from paasng.utils.error_codes import error_codes
from tests.utils.auth import create_user
from tests.utils.helpers import configure_regions, create_app, generate_random_string
from tests.utils.helpers import configure_regions, create_app, generate_random_string, initialize_module

pytestmark = pytest.mark.django_db(databases=["default", "workloads"])

Expand Down Expand Up @@ -756,3 +756,68 @@ def test_issue_count(
for issue in response.data["issue_type_counts"]:
assert issue["issue_type"] in ["none", "idle", "misconfigured"]
assert issue["count"] == 1


class TestDeploymentModuleOrder:
def test_module_order(self, api_client, bk_app, bk_user):
"""
测试部署管理-进程列表模块自定义排序
"""
module = Module.objects.create(
application=bk_app, name="test1", language="python", source_init_template="test1", creator=bk_user
)
initialize_module(module)

module = Module.objects.create(
application=bk_app, name="test2", language="python", source_init_template="test2", creator=bk_user
)
initialize_module(module)

url = reverse("api.applications.deployment.module_order", kwargs={"code": bk_app.code})

response = api_client.post(
url,
data={
"module_orders": [
{
"module_name": "test1",
"order": 1,
},
{
"module_name": "test2",
"order": 2,
},
]
},
)
assert response.status_code == 201

response = api_client.post(
url,
data={
"module_orders": [
{
"module_name": "test1",
"order": 3,
},
{
"module_name": "test2",
"order": 1,
},
]
},
)
assert response.status_code == 201

response = api_client.get(url)
expected_data = [
{
"module_name": "test2",
"order": 1,
},
{
"module_name": "test1",
"order": 3,
},
]
assert response.data == expected_data