今天还是一些CRUD操作,继续分享k8s集群和应用的管理。
class KubernetesCluster(TimeAbstract):
"""
K8s集群配置
"""
name = models.CharField(max_length=100, unique=True, verbose_name='集群名称')
version = models.JSONField(default=dict, verbose_name='版本',
help_text='{"core": "1.14", "apiversion": "apps/v1"}
core: 集群版本
apiversion: API版本')
desc = models.TextField(null=True, blank=True, verbose_name='集群描述')
config = models.JSONField(default=dict, verbose_name='集群配置')
environment = models.ManyToManyField(Environment, related_name='env_k8s', blank=True, verbose_name='环境')
product = models.ManyToManyField(Product, related_name='product_k8s', blank=True, verbose_name='产品')
idc = models.ForeignKey(Idc, blank=True, null=True, on_delete=models.PROTECT, verbose_name='IDC')
def __str__(self):
return self.name
class ExtMeta:
related = True
dashboard = True
icon = 'k8s'
class Meta:
default_permissions = ()
ordering = ['-id']
verbose_name = 'K8s集群'
verbose_name_plural = verbose_name + '管理'
def get_default_value():
return {
'key': 'default', 'value': 'default'
}
# 应用部署方式
G_DEPLOY_TYPE = (
('nonk8s', '非Kubernetes部署'),
('docker', 'Docker部署'),
('k8s', 'Kubernetes部署')
)
G_ONLINE_CHOICE = (
(0, '未上线'),
(1, '已上线'),
(2, '部署中'),
(3, '部署异常'),
(9, '已申请上线')
)
class MicroApp(TimeAbstract):
# product.project.microapp
appid = models.CharField(max_length=250, db_index=True, unique=True, verbose_name='应用ID',
help_text='应用唯一标识,无需填写')
name = models.CharField(max_length=128, verbose_name='应用')
alias = models.CharField(max_length=128, blank=True, verbose_name='别名')
project = models.ForeignKey(Project, on_delete=models.PROTECT, null=True, blank=True, verbose_name='项目')
creator = models.ForeignKey(User, on_delete=models.PROTECT, null=True, blank=True, verbose_name='创建者',
help_text='前端不需要传递')
repo = models.JSONField(default=dict, verbose_name='仓库地址',
help_text='{"name": name, "description": "", "path_with_namespace": "", "http_url_to_repo": url}')
target = models.JSONField(default=get_default_value, verbose_name='JAR包配置',
help_text='默认:default, {"default": "default", "custom": "xxx/a.war"}')
extra_members = models.JSONField(default=get_default_extra_members,
verbose_name="额外成员组", help_text='{"name": "自定义成员组1", members: [1,2,3]}')
category = models.CharField(max_length=128, blank=True, null=True, verbose_name='应用分类')
template = models.JSONField(default=dict, verbose_name='K8sDeployment模板',
help_text='Kubernetes Deployment部署模板配置')
language = models.CharField(max_length=32, default='java', verbose_name='开发语言')
multiple_app = models.BooleanField(default=False, blank=True, verbose_name='多应用标志')
multiple_ids = models.JSONField(default=list, verbose_name='多应用关联ID列表')
dockerfile = models.JSONField(default=get_default_value, verbose_name='Dockerfile配置',
help_text='默认:{default: null}, 可选: {"default|默认": null, "project|使用项目Dockerfile": "project", "custom|自定义Dockerfile": ""}')
online = models.BooleanField(default=True, blank=True, verbose_name='上线下线',
help_text='应用上线/下线状态标记, 下线状态的应用禁止发布.')
desc = models.TextField(verbose_name='描述', null=True, blank=True)
notify = models.JSONField(default=dict, verbose_name='消息通知')
can_edit = models.JSONField(default=list, verbose_name='管理人员',
help_text='有权限编辑该应用的人员ID
格式为数组, 如[1,2]')
is_k8s = models.CharField(max_length=8, default='k8s', choices=G_DEPLOY_TYPE, verbose_name='部署方式',
help_text=f'默认k8s, 可选: {dict(G_DEPLOY_TYPE)}')
modules = models.JSONField(default=list, verbose_name='工程模块')
def __str__(self):
return '[%s]%s' % (self.name, self.alias)
class ExtMeta:
related = True
dashboard = True
icon = 'component'
class Meta:
default_permissions = ()
ordering = ['-created_time']
verbose_name = '应用'
verbose_name_plural = verbose_name + '管理'
class AppInfo(TimeAbstract):
# uniq_tag: product.project.microapp.env
uniq_tag = models.CharField(max_length=128, unique=True, verbose_name='唯一标识', help_text='前端留空,无需传值')
app = models.ForeignKey(MicroApp, blank=True, null=True, on_delete=models.PROTECT, verbose_name='应用')
environment = models.ForeignKey(Environment, on_delete=models.PROTECT, null=True, verbose_name='环境')
branch = models.CharField(max_length=64, blank=True, null=True, verbose_name="默认构建分支")
allow_ci_branch = models.JSONField(default=list, verbose_name='允许构建的分支',
help_text="存储数组格式,具体的分支名; 默认['*'], 表示允许所有分支.")
allow_cd_branch = models.JSONField(default=list, verbose_name='允许发布的分支',
help_text="存储数组格式,具体的分支名; 默认['*'], 表示允许所有分支.")
build_command = models.CharField(max_length=250, blank=True, null=True, verbose_name='构建命令',
help_text='根据应用开发语言, 从getKey("LANGUAGE")获取数据, 取出extra字段的build值')
kubernetes = models.ManyToManyField(KubernetesCluster, related_name='k8s_app', through='KubernetesDeploy',
verbose_name='K8s集群')
hosts = models.JSONField(default=list, verbose_name='部署主机', help_text='部署主机, 格式: []')
template = models.JSONField(default=dict, verbose_name='K8sDeployment模板',
help_text='继承自当前应用的template字段,数据格式为对象
字段说明:
type: 0|1, 0表示继承应用模板,template为空字典;1表示自定义模板
示例: {"type": 0, "template": {}}')
# {0: 禁用, 1: 启用}
is_enable = models.SmallIntegerField(default=1, verbose_name='启用', help_text='状态 {0: 禁用, 1: 启用},默认值为1')
desc = models.TextField(verbose_name='描述', null=True, blank=True)
can_edit = models.JSONField(default=list, verbose_name='管理人员',
help_text='有权限编辑该应用的人员ID
格式为数组, 如[1,2]')
online = models.SmallIntegerField(default=0, choices=G_ONLINE_CHOICE, verbose_name='是否上线',
help_text=f'默认为0,即未上线
可选项: {G_ONLINE_CHOICE}')
def __str__(self):
return self.uniq_tag
@property
def namespace(self):
return f'{self.environment.name.replace("_", "-")}-{self.app.project.name.replace("_", "-")}'.lower()
@property
def jenkins_jobname(self):
try:
job_name = f'{self.environment.name}-{self.app.category.split(".")[-1]}-{self.app.project.name}-{self.app.name.split(".")[-1]}'.lower()
except AppInfo.DoesNotExist:
job_name = ''
return job_name
class ExtMeta:
related = True
dashboard = True
class Meta:
default_permissions = ()
ordering = ['-update_time', '-id']
verbose_name = '应用模块'
verbose_name_plural = verbose_name + '管理'
class KubernetesDeploy(TimeAbstract):
appinfo = models.ForeignKey(AppInfo, related_name='app_info', null=True, on_delete=models.CASCADE)
kubernetes = models.ForeignKey(KubernetesCluster, related_name='app_k8s', null=True, on_delete=models.CASCADE)
online = models.SmallIntegerField(default=0, choices=G_ONLINE_CHOICE, verbose_name='是否上线',
help_text=f'默认为0,即未上线
可选项: {G_ONLINE_CHOICE}')
version = models.CharField(max_length=250, blank=True, null=True, verbose_name='当前版本')
def __str__(self):
return '%s-%s' % (self.appinfo.app.appid, self.kubernetes.name)
class Meta:
default_permissions = ()
class DataDict(CommonParent):
key = models.CharField(max_length=80, unique=True, verbose_name='键')
value = models.CharField(max_length=80, verbose_name='值')
extra = models.TextField(null=True, blank=True, default='', verbose_name='额外参数')
desc = models.CharField(max_length=255, blank=True, null=True, verbose_name='备注')
def __str__(self):
return self.value
class Meta:
default_permissions = ()
verbose_name = '字典'
verbose_name_plural = verbose_name + '管理'
import json
from typing import List
from rest_framework import serializers
from django.db import transaction
from django.contrib.auth.models import User
from cmdb.models import Product, Project, Environment, KubernetesCluster, MicroApp, AppInfo, KubernetesDeploy
class KubernetesClusterListSerializers(serializers.ModelSerializer):
config = serializers.SerializerMethodField()
def get_config(self, instance):
return json.loads(instance.config)
class Meta:
model = KubernetesCluster
fields = '__all__'
class KubernetesClusterSerializers(serializers.ModelSerializer):
class Meta:
model = KubernetesCluster
fields = '__all__'
class MicroAppListSerializers(serializers.ModelSerializer):
project_info = serializers.SerializerMethodField()
appinfo = serializers.SerializerMethodField()
creator_info = serializers.SerializerMethodField()
extra_team_info = serializers.SerializerMethodField()
def get_project_info(self, instance):
project = instance.project
return {'project': {'id': project.id, 'alias': project.alias},
'product': {'id': project.product.id, 'alias': project.product.alias}}
def get_appinfo(self, instance):
return [
{'id': i.id, 'env_alias': i.environment.alias, 'env': {'name': i.environment.name, 'id': i.environment.id},
'online': i.online} for i in instance.appinfo_set.all()]
def get_creator_info(self, instance):
try:
return {'id': instance.creator.id, 'first_name': instance.creator.first_name,
'username': instance.creator.username}
except BaseException as e:
return {'id': '', 'first_name': '', 'username': ''}
def get_extra_team_info(self, instance):
data = {}
for k, v in instance.extra_members.items():
data[k] = [
{'id': i.id, 'name': i.name, 'first_name': i.first_name, 'username': i.username}
for i in User.objects.filter(id__in=v)
]
return data
class Meta:
model = MicroApp
fields = '__all__'
class MicroAppSerializers(serializers.ModelSerializer):
class Meta:
model = MicroApp
fields = '__all__'
read_only_fields = ('appid',)
@staticmethod
def perform_extend_save(validated_data):
def default_value(fields: List):
for field in fields:
if validated_data.get(field):
if validated_data[field].get('key') != 'custom':
validated_data[field]['value'] = validated_data[field]['key']
return validated_data
validated_data = default_value(['dockerfile', 'target'])
validated_data[
'appid'] = f"{validated_data['project'].product.name}.{validated_data['project'].name}.{validated_data['name']}"
return validated_data
def create(self, validated_data):
instance = MicroApp.objects.create(can_edit=[validated_data['creator'].id],
**self.perform_extend_save(validated_data))
return instance
def update(self, instance, validated_data):
return super().update(instance, self.perform_extend_save(validated_data))
class AppInfoListSerializers(serializers.ModelSerializer):
app = MicroAppSerializers()
kubernetes_info = serializers.SerializerMethodField()
def get_kubernetes_info(self, instance):
serializer = KubernetesDeploySerializers(data=KubernetesDeploy.objects.filter(appinfo=instance.id), many=True)
serializer.is_valid()
return serializer.data
class Meta:
model = AppInfo
fields = '__all__'
class AppInfoSerializers(serializers.ModelSerializer):
class Meta:
model = AppInfo
fields = '__all__'
def perform_extend_save(self, validated_data, *args, **kwargs):
if validated_data.get('app', None) and validated_data.get('environment', None):
validated_data[
'uniq_tag'] = f"{validated_data['app'].appid}.{validated_data['environment'].name.split('_')[-1].lower()}"
if kwargs.get('instance', None):
kubernetes = self.initial_data.get('kubernetes')
_bulk = []
for kid in kubernetes:
_ks = KubernetesCluster.objects.get(id=kid)
_bulk.append(KubernetesDeploy(appinfo=kwargs['instance'], kubernetes=_ks))
KubernetesDeploy.objects.bulk_create(_bulk, ignore_conflicts=True)
return validated_data
@transaction.atomic
def create(self, validated_data):
instance = AppInfo.objects.create(**self.perform_extend_save(validated_data))
if 'kubernetes' in self.initial_data:
self.perform_extend_save(validated_data, **{'instance': instance})
return instance
@transaction.atomic
def update(self, instance, validated_data):
KubernetesDeploy.objects.filter(appinfo=instance).delete()
instance.__dict__.update(**self.perform_extend_save(validated_data, **{'instance': instance}))
instance.save()
return instance
from rest_framework.response import Response
from rest_framework.decorators import action
from django.db import transaction
from cmdb.models import Product, Project, Environment, KubernetesCluster, MicroApp, AppInfo
from cmdb.serializers import AppInfoListSerializers, AppInfoSerializers, KubernetesClusterListSerializers, KubernetesClusterSerializers, MicroAppListSerializers, MicroAppSerializers
from common.extends.decorators import cmdb_app_unique_check
class KubernetesClusterViewSet(viewsets.ModelViewSet):
"""
Kubernetes集群视图
### Kubernetes集群权限
{'*': ('k8scluster_all', 'k8s集群管理')},
{'get': ('k8scluster_list', '查看k8s集群')},
{'post': ('k8scluster_create', '创建k8s集群')},
{'put': ('k8scluster_edit', '编辑k8s集群')},
{'patch': ('k8scluster_edit', '编辑k8s集群')},
{'delete': ('k8scluster_delete', '删除k8s集群')}
"""
perms_map = (
{'*': ('admin', '管理员')},
{'*': ('k8scluster_all', 'k8s集群管理')},
{'get': ('k8scluster_list', '查看k8s集群')},
{'post': ('k8scluster_create', '创建k8s集群')},
{'put': ('k8scluster_edit', '编辑k8s集群')},
{'patch': ('k8scluster_edit', '编辑k8s集群')},
{'delete': ('k8scluster_delete', '删除k8s集群')}
)
queryset = KubernetesCluster.objects.all()
serializer_class = KubernetesClusterSerializers
def get_serializer_class(self):
if self.action in ['list', 'retrieve']:
return KubernetesClusterListSerializers
return KubernetesClusterSerializers
class MicroAppViewSet(viewsets.ModelViewSet):
"""
项目应用视图
### 项目应用权限
{'*': ('microapp_all', '应用管理')},
{'get': ('microapp_list', '查看应用')},
{'post': ('microapp_create', '创建应用')},
{'put': ('microapp_edit', '编辑应用')},
{'patch': ('microapp_edit', '编辑应用')},
{'delete': ('microapp_delete', '删除应用')}
"""
perms_map = (
{'*': ('admin', '管理员')},
{'*': ('microapp_all', '应用管理')},
{'get': ('microapp_list', '查看应用')},
{'post': ('microapp_create', '创建应用')},
{'put': ('microapp_edit', '编辑应用')},
{'patch': ('microapp_edit', '编辑应用')},
{'delete': ('microapp_delete', '删除应用')}
)
queryset = MicroApp.objects.all()
serializer_class = MicroAppSerializers
def get_serializer_class(self):
if self.action in ['list', 'retrieve']:
return MicroAppListSerializers
return MicroAppSerializers
@cmdb_app_unique_check()
def create(self, request, *args, **kwargs):
"""
创建应用
提交参数
创建:{}
"""
try:
request.data['name'] = request.data['name'].strip(' ').replace(' ', '-')
except BaseException as e:
logger.error('exception ', str(e))
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
return Response({'code': 40000, 'status': 'failed', 'message': serializer.errors})
try:
self.perform_create(serializer)
except BaseException as e:
logger.error(e)
return Response({'code': 50000, 'status': 'failed', 'message': str(e)})
data = {'data': serializer.data, 'status': 'success', 'code': 20000}
return Response(data)
@transaction.atomic
def perform_create(self, serializer):
serializer.save(creator=self.request.user)
@action(methods=['POST'], url_path='related', detail=False)
def app_related(self, request):
"""
应用关联
### 传递参数:
ids: 待关联应用id数组
target: 目标应用id
"""
try:
target = request.data.get('target', None)
ids = request.data.get('ids', None)
if target:
instance = self.queryset.get(id=target)
ids.extend(instance.multiple_ids)
self.queryset.filter(id__in=list(set(ids))).update(multiple_app=True, multiple_ids=list(set(ids)))
return Response({'code': 20000, 'data': '应用关联成功.'})
except BaseException as e:
logger.error('err', e)
return Response({'code': 50000, 'data': '关联应用异常,请联系管理员!'})
@action(methods=['POST'], url_path='unrelated', detail=False)
def app_unrelated(self, request):
"""
取消应用关联
### 传递参数:
id: 应用id
"""
try:
instance = self.queryset.filter(id=request.data.get('id'))
# 获取关联应用ID列表
ids = instance[0].multiple_ids
ids.remove(instance[0].id)
if len(ids) == 1:
# 如果关联应用只剩下一个,则一起取消关联
self.queryset.filter(id__in=instance[0].multiple_ids).update(multiple_app=False, multiple_ids=[])
else:
# 更新其它应用的关联应用ID
self.queryset.filter(id__in=ids).update(multiple_ids=ids)
# 取消当前实例应用关联
instance.update(multiple_app=False, multiple_ids=[])
return Response({'code': 20000, 'data': '应用取消关联成功.'})
except BaseException as e:
return Response({'code': 50000, 'data': '关联应用异常,请联系管理员!'})
class AppInfoViewSet(viewsets.ModelViewSet):
"""
项目应用服务
* 服务对应着应用的不同环境,即应用每个环境创建一个对应的服务
### 项目应用服务权限
{'*': ('microapp_all', '应用管理')},
{'get': ('microapp_list', '查看应用')},
{'post': ('microapp_create', '创建应用')},
{'put': ('microapp_edit', '编辑应用')},
{'patch': ('microapp_edit', '编辑应用')},
{'delete': ('microapp_delete', '删除应用')}
"""
perms_map = (
{'*': ('admin', '管理员')},
{'*': ('microapp_all', '应用管理')},
{'get': ('microapp_list', '查看应用')},
{'post': ('microapp_create', '创建应用')},
{'put': ('microapp_edit', '编辑应用')},
{'patch': ('microapp_edit', '编辑应用')},
{'delete': ('microapp_delete', '删除应用')}
)
queryset = AppInfo.objects.all()
serializer_class = AppInfoSerializers
def get_serializer_class(self):
if self.action in ['list', 'retrieve']:
return AppInfoListSerializers
return AppInfoSerializers
def create(self, request, *args, **kwargs):
request.data['uniq_tag'] = 'default'
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
data = serializer.data
data = {'code': 20000, 'status': 200, 'data': data}
return Response(data)
细心的朋友应该可以发现,在视图里文件里导入一个自己编写的装饰器
from common.extends.decorators import cmdb_app_unique_check
我们先在项目根下创建common目录
(venv) ydevops-backend mkdir -p common/extends
(venv) ydevops-backend touch common/extends/decorators.py
然后编写装饰器cmdb_app_unique_check
from functools import wraps
from rest_framework.response import Response
from cmdb.models import MicroApp
def cmdb_app_unique_check():
"""
应用唯一性检查
appid: {product.name}.{app.name}
"""
def check_app(product, name):
try:
if MicroApp.objects.filter(project__product__id=product, name=name).exists():
# 存在应用
return True
except BaseException as e:
pass
return False
def decorator(func):
@wraps(func)
def wrapper(self, request, *args, **kwargs):
if check_app(request.data['product'], request.data['name']):
return Response({'code': 50000, 'message': f'该产品下已存在[{request.data["name"]}]同名应用.'})
return func(self, request, *args, **kwargs)
return wrapper
return decorator
from django.contrib import admin
from django.urls import path, include
from cmdb.view.view_cmdb import AppInfoViewSet, KubernetesClusterViewSet, MicroAppViewSet
from rest_framework.routers import DefaultRouter
from cmdb.views import RegionViewSet, IdcViewSet, ProductViewSet, ProjectViewSet, EnvironmentViewSet
router = DefaultRouter()
router.register('region', RegionViewSet)
router.register('asset/idc', IdcViewSet)
router.register('product', ProductViewSet)
router.register('project', ProjectViewSet)
router.register('environment', EnvironmentViewSet)
router.register('app/service', AppInfoViewSet)
router.register('app', MicroAppViewSet)
router.register('kubernetes', KubernetesClusterViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
]
(venv) ydevops-backend python manage.py makemigrations
Migrations for 'cmdb':
apps/cmdb/migrations/0003_appinfo_kubernetescluster_microapp_kubernetesdeploy_and_more.py
- Create model AppInfo
- Create model KubernetesCluster
- Create model MicroApp
- Create model KubernetesDeploy
- Add field app to appinfo
- Add field environment to appinfo
- Add field kubernetes to appinfo
(venv) ydevops-backend python manage.py makemigrations
Migrations for 'cmdb':
apps/cmdb/migrations/0004_datadict.py
- Create model DataDict
(venv) ydevops-backend python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, cmdb, contenttypes, sessions
Running migrations:
Applying cmdb.0003_appinfo_kubernetescluster_microapp_kubernetesdeploy_and_more... OK
Applying cmdb.0004_datadict... OK
(venv) ydevops-backend python manage.py runserver 0.0.0.0:9000
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
March 23, 2023 - 22:13:47
Django version 4.1.7, using settings 'devops_backend.settings'
Starting development server at http://0.0.0.0:9000/
Quit the server with CONTROL-C.
打开浏览器访问http://localhost:9000/api/就可以看到目前已完成的接口了
页面更新:2024-03-13
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号