diff --git a/spug_api/apps/app/__init__.py b/spug_api/apps/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/spug_api/apps/app/models.py b/spug_api/apps/app/models.py new file mode 100644 index 0000000..aa6a2ba --- /dev/null +++ b/spug_api/apps/app/models.py @@ -0,0 +1,82 @@ +from django.db import models +from libs import ModelMixin, human_time +from apps.account.models import User +from apps.config.models import Environment +import json + + +class App(models.Model, ModelMixin): + EXTENDS = ( + ('1', '常规发布'), + ('2', '自定义发布'), + ) + name = models.CharField(max_length=50) + env = models.ForeignKey(Environment, on_delete=models.PROTECT) + host_ids = models.TextField() + extend = models.CharField(max_length=2, choices=EXTENDS) + is_audit = models.BooleanField() + + created_at = models.CharField(max_length=20, default=human_time) + created_by = models.ForeignKey(User, models.PROTECT, related_name='+') + updated_at = models.CharField(max_length=20, null=True) + updated_by = models.ForeignKey(User, models.PROTECT, related_name='+', null=True) + + @property + def extend_obj(self): + cls = AppExtend1 if self.extend == '1' else AppExtend2 + return cls.objects.filter(app=self).first() + + def to_dict(self, *args, **kwargs): + app = super().to_dict(*args, **kwargs) + app['host_ids'] = json.loads(self.host_ids) + app.update(self.extend_obj.to_dict()) + return app + + def __repr__(self): + return '' % self.name + + class Meta: + db_table = 'apps' + ordering = ('-id',) + + +class AppExtend1(models.Model, ModelMixin): + GIT_TYPES = ( + ('tag', 'tag'), + ('branch', 'branch') + ) + app = models.OneToOneField(App, primary_key=True, on_delete=models.CASCADE) + git_repo = models.CharField(max_length=255) + git_type = models.CharField(max_length=10, choices=GIT_TYPES) + dst_dir = models.CharField(max_length=255) + dst_repo = models.CharField(max_length=255) + versions = models.IntegerField() + filter_rule = models.TextField() + custom_envs = models.TextField() + hook_pre_server = models.TextField(null=True) + hook_post_server = models.TextField(null=True) + hook_pre_host = models.TextField(null=True) + hook_post_host = models.TextField(null=True) + + def to_dict(self, *args, **kwargs): + tmp = super().to_dict(*args, **kwargs) + tmp['filter_rule'] = json.loads(self.filter_rule) + tmp['custom_envs'] = '\n'.join(f'{k}={v}' for k, v in json.loads(self.custom_envs).items()) + return tmp + + def __repr__(self): + return '' % self.app_id + + class Meta: + db_table = 'app_extend1' + + +class AppExtend2(models.Model, ModelMixin): + app = models.OneToOneField(App, primary_key=True, on_delete=models.CASCADE) + actions = models.TextField() + + def __repr__(self): + return '' % self.app_id + + class Meta: + db_table = 'app_extend2' diff --git a/spug_api/apps/app/urls.py b/spug_api/apps/app/urls.py new file mode 100644 index 0000000..e24322a --- /dev/null +++ b/spug_api/apps/app/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from .views import * + +urlpatterns = [ + path('', AppView.as_view()), +] diff --git a/spug_api/apps/app/views.py b/spug_api/apps/app/views.py new file mode 100644 index 0000000..e7ca592 --- /dev/null +++ b/spug_api/apps/app/views.py @@ -0,0 +1,66 @@ +from django.views.generic import View +from libs import JsonParser, Argument, json_response +from apps.app.models import App, AppExtend1, AppExtend2 +import json + + +class AppView(View): + def get(self, request): + apps = App.objects.all() + return json_response(apps) + + def post(self, request): + form, error = JsonParser( + Argument('id', type=int, required=False), + Argument('name', help='请输入应用名称'), + Argument('env_id', type=int, help='请选择环境'), + Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择要部署的主机'), + Argument('extend', filter=lambda x: x in dict(App.EXTENDS), help='请选择发布类型'), + Argument('is_audit', type=bool, default=False) + ).parse(request.body) + if error is None: + form.host_ids = json.dumps(form.host_ids) + if form.extend == '1': + extend_form, error = JsonParser( + Argument('git_repo', handler=str.strip, help='请输入git仓库地址'), + Argument('git_type', help='参数错误'), + Argument('dst_dir', handler=str.strip, help='请输入发布目标路径'), + Argument('dst_repo', handler=str.strip, help='请输入目标仓库路径'), + Argument('versions', type=int, help='请输入保留历史版本数量'), + Argument('filter_rule', type=dict, help='参数错误'), + Argument('custom_envs', handler=str.strip, required=False), + Argument('hook_pre_server', handler=str.strip, default=''), + Argument('hook_post_server', handler=str.strip, default=''), + Argument('hook_pre_host', handler=str.strip, default=''), + Argument('hook_post_host', handler=str.strip, default='') + ).parse(request.body) + if error: + return json_response(error=error) + extend_form.filter_rule = json.dumps(extend_form.filter_rule) + extend_form.custom_envs = json.dumps(parse_envs(extend_form.custom_envs)) + if form.id: + App.objects.filter(pk=form.id).update(**form) + AppExtend1.objects.filter(app_id=form.id).update(**extend_form) + else: + app = App.objects.create(created_by=request.user, **form) + AppExtend1.objects.create(app=app, **extend_form) + elif form.extend == '2': + extend_form, error = JsonParser( + Argument('actions', type=list, filter=lambda x: len(x), help='请输入执行动作') + ).parse(request.body) + if error: + return json_response(error=error) + app = App.objects.create(created_by=request.user, **form) + AppExtend2.objects.create(app=app, actions=json.dumps(extend_form.actions)) + return json_response(error=error) + + +def parse_envs(data): + tmp = {} + if data: + for line in data.split('\n'): + fields = line.split('=', 1) + if len(fields) != 2 or fields[0].strip() == '': + raise Exception(f'解析自定义全局变量{line!r}失败,确认其遵循 key = value 格式') + tmp[fields[0].strip()] = fields[1].strip() + return tmp diff --git a/spug_api/apps/config/views.py b/spug_api/apps/config/views.py index b796aa4..d356452 100644 --- a/spug_api/apps/config/views.py +++ b/spug_api/apps/config/views.py @@ -206,11 +206,11 @@ def parse_text(request): Argument('o_id', type=int, help='缺少必要参数'), Argument('type', filter=lambda x: x in dict(Config.TYPES), help='缺少必要参数'), Argument('env_id', type=int, help='缺少必要参数'), - Argument('data', help='缺少必要参数') + Argument('data', handler=str.strip, help='缺少必要参数') ).parse(request.body) if error is None: data = {} - for line in form.pop('data').strip().split('\n'): + for line in form.pop('data').split('\n'): fields = line.split('=', 1) if len(fields) != 2 or fields[0].strip() == '': return json_response(error=f'解析配置{line!r}失败,确认其遵循 key = value 格式') diff --git a/spug_api/libs/parser.py b/spug_api/libs/parser.py index abc6c60..da6b945 100644 --- a/spug_api/libs/parser.py +++ b/spug_api/libs/parser.py @@ -17,7 +17,8 @@ class Argument(object): :param bool required: is required """ - def __init__(self, name, default=None, required=True, type=str, filter=None, help=None, nullable=False): + def __init__(self, name, default=None, handler=None, required=True, type=str, filter=None, help=None, + nullable=False): self.name = name self.default = default self.type = type @@ -25,6 +26,7 @@ class Argument(object): self.nullable = nullable self.filter = filter self.help = help + self.handler = handler if not isinstance(self.name, str): raise TypeError('Argument name must be string') if filter and not callable(self.filter): @@ -63,6 +65,8 @@ class Argument(object): if not self.filter(value): raise ParseError( self.help or 'Value Error: %s filter check failed' % self.name) + if self.handler: + value = self.handler(value) return value diff --git a/spug_api/spug/settings.py b/spug_api/spug/settings.py index 55c70d8..9ea2e38 100644 --- a/spug_api/spug/settings.py +++ b/spug_api/spug/settings.py @@ -38,6 +38,7 @@ INSTALLED_APPS = [ 'apps.monitor', 'apps.alarm', 'apps.config', + 'apps.app', ] MIDDLEWARE = [ diff --git a/spug_api/spug/urls.py b/spug_api/spug/urls.py index 80c745c..0d75863 100644 --- a/spug_api/spug/urls.py +++ b/spug_api/spug/urls.py @@ -24,4 +24,5 @@ urlpatterns = [ path('alarm/', include('apps.alarm.urls')), path('setting/', include('apps.setting.urls')), path('config/', include('apps.config.urls')), + path('app/', include('apps.app.urls')), ]