A api update

pull/22/head
雷二猛 2019-12-14 00:36:55 +08:00
parent 0390a9003a
commit dc4b1cd5bf
8 changed files with 164 additions and 3 deletions

View File

View File

@ -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 '<App %r>' % 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 '<AppExtend1 app_id=%r>' % 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 '<AppExtend2 app_id=%r>' % self.app_id
class Meta:
db_table = 'app_extend2'

View File

@ -0,0 +1,7 @@
from django.urls import path
from .views import *
urlpatterns = [
path('', AppView.as_view()),
]

View File

@ -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

View File

@ -206,11 +206,11 @@ def parse_text(request):
Argument('o_id', type=int, help='缺少必要参数'), Argument('o_id', type=int, help='缺少必要参数'),
Argument('type', filter=lambda x: x in dict(Config.TYPES), help='缺少必要参数'), Argument('type', filter=lambda x: x in dict(Config.TYPES), help='缺少必要参数'),
Argument('env_id', type=int, help='缺少必要参数'), Argument('env_id', type=int, help='缺少必要参数'),
Argument('data', help='缺少必要参数') Argument('data', handler=str.strip, help='缺少必要参数')
).parse(request.body) ).parse(request.body)
if error is None: if error is None:
data = {} data = {}
for line in form.pop('data').strip().split('\n'): for line in form.pop('data').split('\n'):
fields = line.split('=', 1) fields = line.split('=', 1)
if len(fields) != 2 or fields[0].strip() == '': if len(fields) != 2 or fields[0].strip() == '':
return json_response(error=f'解析配置{line!r}失败,确认其遵循 key = value 格式') return json_response(error=f'解析配置{line!r}失败,确认其遵循 key = value 格式')

View File

@ -17,7 +17,8 @@ class Argument(object):
:param bool required: is required :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.name = name
self.default = default self.default = default
self.type = type self.type = type
@ -25,6 +26,7 @@ class Argument(object):
self.nullable = nullable self.nullable = nullable
self.filter = filter self.filter = filter
self.help = help self.help = help
self.handler = handler
if not isinstance(self.name, str): if not isinstance(self.name, str):
raise TypeError('Argument name must be string') raise TypeError('Argument name must be string')
if filter and not callable(self.filter): if filter and not callable(self.filter):
@ -63,6 +65,8 @@ class Argument(object):
if not self.filter(value): if not self.filter(value):
raise ParseError( raise ParseError(
self.help or 'Value Error: %s filter check failed' % self.name) self.help or 'Value Error: %s filter check failed' % self.name)
if self.handler:
value = self.handler(value)
return value return value

View File

@ -38,6 +38,7 @@ INSTALLED_APPS = [
'apps.monitor', 'apps.monitor',
'apps.alarm', 'apps.alarm',
'apps.config', 'apps.config',
'apps.app',
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -24,4 +24,5 @@ urlpatterns = [
path('alarm/', include('apps.alarm.urls')), path('alarm/', include('apps.alarm.urls')),
path('setting/', include('apps.setting.urls')), path('setting/', include('apps.setting.urls')),
path('config/', include('apps.config.urls')), path('config/', include('apps.config.urls')),
path('app/', include('apps.app.urls')),
] ]