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: 内置插件支持系统级别配置到特定空间使用 #104 #105

Merged
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
28 changes: 16 additions & 12 deletions bkflow/plugin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,37 @@

from django.db import models

from bkflow.plugin.space_plugin_config_parser import SpacePluginConfigParser

class SpacePluginConfigManager(models.Manager):
def get_space_allow_list(self, space_id):
qs = self.filter(space_id=space_id)
if not qs.exists():
return []
return qs.first().allow_list


class SpacePluginConfig(models.Model):
"""
空间插件配置, Deprecated, 使用 SpaceConfig 替代
config格式如:
{
"default": {"mode": "allow_list", "plugin_codes": ["plugin1", "plugin2"]},
"{scope_type}_{scope_id}": {"mode": "deny_list", "plugin_codes": ["plugin3"]
}
default 字段必须存在,表示默认配置,落库之前通过SpacePluginConfigParser进行校验
插件空间配置, 系统级配置,用于限制内置插件的空间使用范围
config格式如: {"allow_list": ["plugin_code1", "plugin_code2"]}
"""

ALLOW_LIST = "allow_list"

space_id = models.IntegerField(verbose_name="空间ID", db_index=True)
config = models.JSONField(verbose_name="插件配置")
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")

objects = SpacePluginConfigManager()

class Meta:
verbose_name = "空间插件配置"
verbose_name_plural = "空间插件配置"

def __str__(self):
return f"{self.space_id}: {self.config}"

def clean(self):
parser = SpacePluginConfigParser(self.config)
parser.is_valid(raise_exception=True)
return
@property
def allow_list(self):
return self.config.get(self.ALLOW_LIST, [])
15 changes: 15 additions & 0 deletions bkflow/plugin/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from rest_framework import permissions

from bkflow.permission.models import Token


class TokenPluginPermissions(permissions.BasePermission):
"""根据 token 判断用户请求的空间是否对应"""

def has_permission(self, request, view):
token = Token.objects.filter(token=request.token).first()
if not token or token.has_expired():
return False

return int(token.space_id) == int(request.query_params.get("space_id", -1))
14 changes: 14 additions & 0 deletions bkflow/plugin/serializers/comonent.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@

import re

from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from pipeline.component_framework.library import ComponentLibrary
from pipeline.component_framework.models import ComponentModel
from pipeline.exceptions import ComponentNotExistException
from rest_framework import serializers
from rest_framework.exceptions import NotFound

from bkflow.plugin.models import SpacePluginConfig as SpacePluginConfigModel

group_en_pattern = re.compile(r"(?:\()(.*)(?:\))")


Expand Down Expand Up @@ -99,6 +102,17 @@ class ComponentListQuerySerializer(serializers.Serializer):
scope_id = serializers.CharField(help_text="空间下scope ID", required=False)


class ComponentDetailQuerySerializer(serializers.Serializer):
space_id = serializers.CharField(help_text="空间ID")

def validate_space_id(self, space_id):
plugin_code = self.context["plugin_code"]
space_allow_list = SpacePluginConfigModel.objects.get_space_allow_list(space_id)
if plugin_code in settings.SPACE_PLUGIN_LIST and plugin_code not in space_allow_list:
raise serializers.ValidationError(_("插件 {} 不在空间 {} 的插件白名单中").format(plugin_code, space_id))
return space_id


class ComponentModelDetailSerializer(ComponentModelSerializer):
phase = None
sort_key_group_en = None
23 changes: 23 additions & 0 deletions bkflow/plugin/views/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,25 @@
"""
import logging

from django.conf import settings
from django_filters import FilterSet
from drf_yasg.utils import swagger_auto_schema
from pipeline.component_framework.models import ComponentModel

from bkflow.plugin.permissions import TokenPluginPermissions
from bkflow.plugin.serializers.comonent import (
ComponentListQuerySerializer,
ComponentModelDetailSerializer,
ComponentModelListSerializer,
ComponentDetailQuerySerializer,
)
from bkflow.plugin.space_plugin_config_parser import SpacePluginConfigParser
from bkflow.plugin.models import SpacePluginConfig as SpacePluginConfigModel
from bkflow.space.configs import SpacePluginConfig
from bkflow.space.models import SpaceConfig
from bkflow.space.permissions import SpaceSuperuserPermission
from bkflow.utils.mixins import BKFLOWCommonMixin
from bkflow.utils.permissions import AdminPermission
from bkflow.utils.views import ReadOnlyViewSet

logger = logging.getLogger("root")
Expand All @@ -51,10 +57,19 @@ class ComponentModelSetViewSet(BKFLOWCommonMixin, ReadOnlyViewSet):
filterset_class = ComponentModelFilter
pagination_class = None
lookup_field = "code"
permission_classes = [AdminPermission | SpaceSuperuserPermission | TokenPluginPermissions]

def get_queryset(self):
queryset = super().get_queryset()

# 过滤系统配置插件
space_id = self.request.query_params.get("space_id")
system_allow_list = SpacePluginConfigModel.objects.get_space_allow_list(space_id)
space_plugins = set(settings.SPACE_PLUGIN_LIST) - set(system_allow_list)
if space_plugins:
queryset = queryset.exclude(code__in=list(space_plugins))

# 过滤空间配置插件
scope_type = self.request.query_params.get("scope_type")
scope_id = self.request.query_params.get("scope_id")
scope_code = f"{scope_type}_{scope_id}"
Expand All @@ -69,3 +84,11 @@ def list(self, request, *args, **kwargs):
query_ser = ComponentListQuerySerializer(data=request.query_params)
query_ser.is_valid(raise_exception=True)
return super().list(request, *args, **kwargs)

@swagger_auto_schema(query_serializer=ComponentDetailQuerySerializer)
def retrieve(self, request, *args, **kwargs):
query_ser = ComponentDetailQuerySerializer(
data=request.query_params, context={"plugin_code": kwargs[self.lookup_field]}
)
query_ser.is_valid(raise_exception=True)
return super().retrieve(request, *args, **kwargs)
3 changes: 3 additions & 0 deletions config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@
# 忽略 example 插件
ENABLE_EXAMPLE_COMPONENTS = False

# 特定空间插件列表
SPACE_PLUGIN_LIST = env.SPACE_PLUGIN_LIST_STR.split(",") if env.SPACE_PLUGIN_LIST_STR else []

# 静态资源文件(js,css等)在APP上线更新后, 由于浏览器有缓存,
# 可能会造成没更新的情况. 所以在引用静态资源的地方,都把这个加上
# Django 模板中:<script src="/a.js?v="></script>
Expand Down
3 changes: 3 additions & 0 deletions env.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,6 @@

# APP 白名单
APP_WHITE_LIST_STR = os.getenv("BKAPP_APP_WHITE_LIST", "") # 逗号分隔的字符串

# 系统空间插件列表
SPACE_PLUGIN_LIST_STR = os.getenv("SPACE_PLUGIN_LIST_STR", "") # 逗号分隔的字符串
Loading