作一個(gè)記錄泣棋,這版bifang后端數(shù)據(jù)庫(kù)設(shè)計(jì)曙博,以極簡(jiǎn)和高度規(guī)范為原則。配置即代碼抹竹,devops潮流大勢(shì)线罕。
一,base_models
from django.db import models
from simple_history.models import HistoricalRecords
from django.contrib.auth.models import User
# 基礎(chǔ)虛類窃判,所有Model的共同字段钞楼,其它model由此繼承,包括記錄orm操作歷史的history字段兢孝。
class BaseModel(models.Model):
# unique=True用于保證名稱不重復(fù)
name = models.CharField(max_length=100,
unique=True,
verbose_name="名稱")
description = models.CharField(max_length=100,
null=True,
blank=True,
verbose_name="描述")
# 為了避免刪除關(guān)聯(lián)記錄窿凤,bigang里所有外鍵都是on_delete=models.SET_NULL
create_user = models.ForeignKey(User,
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="用戶")
# auto_now用于orm更新記錄時(shí),每次自動(dòng)更新此字段時(shí)間
update_date = models.DateTimeField(auto_now=True)
# auto_now_add只用于第一次新增時(shí)跨蟹,自動(dòng)更新此字段時(shí)間
create_date = models.DateTimeField(auto_now_add=True)
# 用于擴(kuò)展
base_status = models.BooleanField(default=True)
# django的simple_history庫(kù)雳殊,加此字段,用于自動(dòng)保存每個(gè)表的操作歷史
history = HistoricalRecords(inherit=True)
# property用于為orm查詢?cè)黾右粋€(gè)計(jì)算型字段
@property
def username(self):
return self.create_user.username
# 記錄的默認(rèn)顯示值
def __str__(self):
return self.name
class Meta:
# abstract關(guān)鍵字窗轩,表示此class不會(huì)生成數(shù)據(jù)表夯秃,只能被繼承使用
abstract = True
# 默認(rèn)排序規(guī)則,按更新時(shí)間降序
ordering = ('-update_date',)
- django-simple-history的用法痢艺,以后再單文說(shuō)明仓洼。
- 所有的model都繼承自這個(gè)Model,所以這個(gè)model的abstract = True。
- name要求unique堤舒,在設(shè)計(jì)時(shí)要注意條件滿足色建。
- property裝飾符,用于每個(gè)rest的返回值加用戶名舌缤。
- update_date和create_date也為必有字段箕戳,保持每次更新時(shí)間和記錄新增時(shí)間。
二国撵,git_models
from django.db import models
from .base_models import BaseModel
# GitLab代碼倉(cāng)庫(kù)地址陵吸,有的公司可能有多個(gè)Git倉(cāng)庫(kù),所以獨(dú)立出一個(gè)數(shù)據(jù)表
# 如果只有一個(gè)代碼倉(cāng)庫(kù)介牙,當(dāng)然也可以直接在django的settings文件里定義
class GitTb(BaseModel):
# gitlab的URL
git_url = models.URLField(verbose_name="Git API地址")
# 用于python的gitlab庫(kù)去進(jìn)行API 認(rèn)證時(shí)需要
git_token = models.CharField(max_length=64, default='no_token', verbose_name="Git API認(rèn)證token")
# gitlab的版本壮虫,僅于展示記錄,無(wú)實(shí)在用途
git_ver = models.CharField(max_length=16, default='12.10', verbose_name="Git版本")
class Meta:
# 用于定義數(shù)據(jù)表名稱环础,而不使用系統(tǒng)自動(dòng)生成的囚似,統(tǒng)一命名為與類名相同
db_table = 'GitTb'
# 用一起定義admin后臺(tái)顯示名稱(規(guī)則為數(shù)據(jù)表名及簡(jiǎn)短中文)
verbose_name = 'GitTb代碼倉(cāng)庫(kù)'
verbose_name_plural = 'GitTb代碼倉(cāng)庫(kù)'
- db_table均統(tǒng)一命名為與類名相同剩拢。
- Git,Salt谆构,Jenkins這種表裸扶,為了避免與可能的第三方庫(kù)命令相同,無(wú)論是app名稱還是數(shù)據(jù)表名稱搬素,都要注意加一點(diǎn)后綴呵晨。
- verbose_name_plural 和verbose_name相同,命名為表名+中文說(shuō)明熬尺。
- git_token第三方庫(kù)去gitlab拉代碼時(shí)的認(rèn)證摸屠。
三,salt_models
from django.db import models
from .base_models import BaseModel
# Sat Api地址粱哼,不同環(huán)境季二,可能需要不同的Salt Master隔離
# 同樣,如果只有一個(gè)salt master揭措,只需要在django settings文件里定義
class SaltTb(BaseModel):
# bifang項(xiàng)目主要使用了saltypie這個(gè)第三方庫(kù)操作salt-api胯舷,之前的字段,主要是滿足saltypie的認(rèn)證salt-api的參數(shù)
salt_url = models.URLField(verbose_name="Salt API地址")
salt_user = models.CharField(max_length=64, verbose_name="Salt API用戶")
salt_pwd = models.CharField(max_length=64, verbose_name="Salt API密碼")
eauth = models.CharField(max_length=64, default='pam', verbose_name="Salt API用戶認(rèn)證")
trust_host = models.BooleanField(default=True, verbose_name="Salt API安全認(rèn)證")
# 同樣绊含,這個(gè)版本只用于可能的展示桑嘶,現(xiàn)在沒(méi)有使用
salt_ver = models.CharField(max_length=12, default='2019.3010', verbose_name="Salt版本")
class Meta:
db_table = 'SaltTb'
verbose_name = 'SaltTb遠(yuǎn)程執(zhí)行工具'
verbose_name_plural = 'SaltTb遠(yuǎn)程執(zhí)行工具'
- salt和git表類似,一般公司可能是只有一個(gè)躬充,但為了能靈活逃顶,放在數(shù)據(jù)庫(kù)里配置。
- saltypie庫(kù)的初始化如下:
salt = Salt( url=salt_url, username=salt_user, passwd=salt_pwd, trust_host=True, eauth=eauth )
四充甚, env_models
from django.db import models
from .base_models import BaseModel
from .salt_models import SaltTb
# 開(kāi)發(fā)環(huán)境 以政,線上環(huán)境,等等
class Env(BaseModel):
# 因?yàn)槔^續(xù)自BaseModel伴找,所以name,description,user,date那些字段都有了盈蛮,不用重復(fù)
# 使用id,可以在必要時(shí)技矮,減少一些對(duì)數(shù)據(jù)表自增id的依賴眉反,在作數(shù)據(jù)庫(kù)遷移方面,還是有好處的
env_id = models.IntegerField(default=0, verbose_name="環(huán)境Id")
# 一般salt master都是分環(huán)境建的穆役,這樣能達(dá)到批量管理的目的,多套saltmaster又可以隔離安全網(wǎng)絡(luò)
salt = models.ForeignKey(SaltTb,
related_name="ra_env",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="Salt地址")
class Meta:
db_table = 'Env'
verbose_name = 'Env環(huán)境'
verbose_name_plural = 'Env環(huán)境'
- 環(huán)境表梳凛,可能只需要name就可以滿足需求耿币,但為了更有擴(kuò)展性,加一個(gè)env_id韧拒,沒(méi)害處淹接。
- 假定我們可以按環(huán)境可接入salt十性,所以這里加入一個(gè)salt的外鍵字段。
- django 默認(rèn)每個(gè)主表的對(duì)象都有一個(gè)是外鍵的屬性塑悼,可以通過(guò)它來(lái)查詢到所有屬于主表的子表的信息劲适。這個(gè)屬性的名稱默認(rèn)是以子表的名稱小寫(xiě)加上_set()來(lái)表示,默認(rèn)返回的是一個(gè)querydict對(duì)象厢蒜。related_name 可以給這個(gè)外鍵定義好一個(gè)別的名稱霞势。
- related_name我的命名規(guī)則,是ra_開(kāi)頭斑鸦,然后加上表名env愕贡。這樣的約定,更易理解代碼中的引用巷屿。如果有兩個(gè)字段關(guān)聯(lián)到同一個(gè)外鍵表固以,才需要再定義一個(gè)不同的related_name(如server里關(guān)聯(lián)兩個(gè)release,一個(gè)運(yùn)行發(fā)布單 嘱巾,一個(gè)可能的回滾單)憨琳。
- blank=True,null=True,on_delete=models.SET_NULL,這三者定義是關(guān)聯(lián)的。如果我們定義了on_delete策略為models.SET_NULL旬昭,則必須定義null=True篙螟,即允許此字段為空。不然稳懒,在作數(shù)據(jù)庫(kù)的mock數(shù)據(jù)時(shí)闲擦,刪除一個(gè)表的所有記錄時(shí),會(huì)有些細(xì)節(jié)和順序要調(diào)整场梆。而如果使用這三個(gè)值墅冷,則基本無(wú)所謂順序。
- null 是針對(duì)數(shù)據(jù)庫(kù)而言或油,如果 null=True, 表示數(shù)據(jù)庫(kù)的該字段可以為空寞忿。blank 是針對(duì)表單的,如果 blank=True顶岸,表示你的表單填寫(xiě)該字段的時(shí)候可以不填腔彰。
五,project_models
from django.db import models
from .base_models import BaseModel
# 項(xiàng)目辖佣,可表示由多個(gè)相關(guān)微服務(wù)組成的項(xiàng)目
# 比如霹抛,istio里的bookin就算是bifang中的一個(gè)project
# 而其中的productpage,reviews,details,ratings這些應(yīng)用,就相當(dāng)于bifang中的app應(yīng)用
class Project(BaseModel):
# 只是為了一個(gè)中文顯示卷谈,及統(tǒng)一的項(xiàng)目id加的杯拐。不解釋
cn_name = models.CharField(max_length=255, verbose_name="中文名")
project_id = models.IntegerField(default=0, verbose_name="項(xiàng)目編號(hào)")
class Meta:
db_table = 'Project'
verbose_name = 'Project項(xiàng)目'
verbose_name_plural = 'Project項(xiàng)目'
- 無(wú)須解釋,可以用來(lái)表示多含多個(gè)組件的項(xiàng)目,也可以用來(lái)表達(dá)一個(gè)含有很多微服務(wù)的項(xiàng)目總和端逼,方便分類和管理朗兵。
- 一般項(xiàng)目命令為英文字母,加一個(gè)中文別名和項(xiàng)目id顶滩,更易溝通和語(yǔ)義定義余掖。
- istio的bookinfo項(xiàng)目詳情:http://www.reibang.com/c/8572c5dbba97
六,app_models
from django.db import models
from .base_models import BaseModel
from .git_models import GitTb
from .project_models import Project
# 應(yīng)用服務(wù)礁鲁,相當(dāng)于一個(gè)可獨(dú)立部署的微服務(wù)
# 如istio bookinfo中盐欺,可獨(dú)立部署,且使用不同語(yǔ)言開(kāi)發(fā)的4個(gè)應(yīng)用(productpage, details, reviews, ratings)
class App(BaseModel):
# cn_name和app_id意義和project中的一樣救氯。但要注意找田,這個(gè)app_id,會(huì)和一些外鍵表中的app_id有雷同着憨,這是高級(jí)技巧了墩衙,
# 這是一個(gè)坑,但本來(lái)這樣命名甲抖,也是最自然的漆改,無(wú)所畏懼。
cn_name = models.CharField(max_length=255, verbose_name="中文名")
app_id = models.IntegerField(default=0, verbose_name="應(yīng)用編號(hào)")
# 每個(gè)app應(yīng)用准谚,與一個(gè)代碼關(guān)聯(lián)
git = models.ForeignKey(GitTb,
related_name="ra_app",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="Git實(shí)例")
# 這里單獨(dú)定義一個(gè)git中這個(gè)app的id挫剑,可能有優(yōu)化的空間,也可能沒(méi)有柱衔。git庫(kù)在定義具體的代碼倉(cāng)庫(kù)時(shí)樊破,就是要這個(gè)參數(shù)
git_app_id = models.IntegerField(default=0, verbose_name="Git應(yīng)用ID")
# 為了加強(qiáng)控制,git中每一個(gè)ci/cd功能唆铐,當(dāng)代碼提交之后哲戚,都不會(huì)自動(dòng)運(yùn)行,而要通過(guò)一個(gè)trigger token去人工觸發(fā)
# 這個(gè)trigger token的生成艾岂,在gitlab的ci/cd里顺少,很容易生成。
git_trigger_token = models.CharField(max_length=64,
blank=True,
null=True,
verbose_name="git trigger token")
# 將app與project進(jìn)行關(guān)聯(lián)王浴,方便數(shù)據(jù)統(tǒng)計(jì)脆炎,關(guān)聯(lián)顯示
project = models.ForeignKey(Project,
related_name='ra_project',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name='項(xiàng)目')
# 按devops及gitops的理念,開(kāi)發(fā)維護(hù)自己的編譯腳本和部署腳本氓辣,并進(jìn)行版本管理
# build_script用于定義通過(guò)構(gòu)建秒裕,生成軟件包的腳本
build_script = models.CharField(max_length=255, default='build.sh', verbose_name="編譯腳本名")
# deploy_script用于定義服務(wù)部署,啟停钞啸,備份几蜻,回滾癞松,健康狀態(tài)檢測(cè)等功能,
# 如果為開(kāi)發(fā)提供了模板入蛆,是很容易作為代碼一部份管理起來(lái)的,配置即代碼硕勿!
# 如果bifang本身來(lái)存儲(chǔ)這些腳本哨毁,反而增加管理難度,不透明度及中心化
deploy_script = models.CharField(max_length=255, default='bifang.sh', verbose_name="部署腳本名")
# 定義軟件包的名稱源武,這里也有糾結(jié)扼褪,是自定義,還是強(qiáng)約定
# 如果規(guī)定了軟件包名必須與app名稱相同粱栖,會(huì)少很多事话浇,但又顯得過(guò)于霸道
# 這里留個(gè)小口吧,另外闹究,在部署腳本時(shí)幔崖,還會(huì)有幾個(gè)類似的軟件包,軟件壓縮包名渣淤,軟件部署目錄赏寇,都類似
zip_package_name = models.CharField(max_length=255, default='demo.zio', verbose_name="應(yīng)用壓縮包")
# 如果app有腳本端口,則可能用于狀態(tài)檢測(cè)价认,增加部署的成功判斷率
service_port = models.IntegerField(default=0, verbose_name="服務(wù)端口")
# 使用哪個(gè)用戶名和哪個(gè)用戶組啟動(dòng)嗅定,
service_username = models.CharField(max_length=24,
blank=True,
null=True,
verbose_name="執(zhí)行用戶名")
service_group = models.CharField(max_length=24,
blank=True,
null=True,
verbose_name="執(zhí)行用戶組")
# 如果bifang增加獨(dú)立的服務(wù)器啟停功能,日志單獨(dú)數(shù)據(jù)表保存用踩,但這里可以保存最近啟停次數(shù)渠退,用于定義日志
op_log_no = models.IntegerField(default=0, verbose_name="啟停日志次數(shù)")
class Meta:
db_table = 'App'
verbose_name = 'App應(yīng)用'
verbose_name_plural = 'App應(yīng)用'
- cn_name和app_id,和Project字段同義脐彩。
- 每個(gè)app屬于一個(gè)project碎乃。所以它們之間有一對(duì)多的關(guān)系。
- 使用django model orm的外鍵定義丁屎,主要是在查詢方便荠锭,有很強(qiáng)大的語(yǔ)法支持。在更新時(shí)晨川,orm更易和python的數(shù)據(jù)結(jié)構(gòu)交互证九。
- git和git_repo用于定義此app的git地址,方便bifang對(duì)此代碼庫(kù)進(jìn)行ci/cd操作共虑。
- service_username和service_group愧怜,用于調(diào)用salt進(jìn)行遠(yuǎn)程部署時(shí),指定用戶名和用戶組妈拌。一般來(lái)說(shuō)拥坛,用戶名足夠了蓬蝶。但,萬(wàn)一有用戶的要求呢猜惋?留有擴(kuò)展丸氛。
- op_log_no 字段,用于定位此app組件的啟停批次著摔,方便查看日志時(shí)的定義缓窜。當(dāng)然,也可以在此app組件下的每個(gè)server中谍咆,使用此字段禾锤。但那樣ms管理有點(diǎn)不集中了。待優(yōu)化~
七摹察,server_models
from django.db import models
from .base_models import BaseModel
from .app_models import App
from .release_models import Release
SYSTEM_CHOICES = (
('WINDOWS', 'WINDOWS'),
('LINUX', 'LINUX'),
)
# 部署服務(wù)器恩掷,保證ip和port結(jié)合起來(lái)的唯一性,可以一個(gè)服務(wù)器上部署多個(gè)應(yīng)用
# 當(dāng)然供嚎,能在同一個(gè)服務(wù)器上黄娘,部署多個(gè)相同的應(yīng)用,這得益于將部署腳本讓開(kāi)發(fā)自己維護(hù)查坪。
# 真正的devops團(tuán)隊(duì)寸宏,是需要都有開(kāi)發(fā)和運(yùn)維的跨界實(shí)力的啦~
class Server(BaseModel):
# GenericIPAddressField的字段,讓這里只存儲(chǔ)ip地址
ip = models.GenericIPAddressField(max_length=64, verbose_name="服務(wù)器Ip")
# 服務(wù)端口偿曙,其實(shí)氮凝,這里是需要優(yōu)化的,
# 如果有的服務(wù)啟動(dòng)望忆,不提供端口服務(wù)呢罩阵?亂寫(xiě)么?如何保證多個(gè)無(wú)端口服務(wù)不沖突启摄?
port = models.IntegerField(verbose_name="服務(wù)器端口")
system_type = models.CharField(max_length=16,
choices=SYSTEM_CHOICES,
default='LINUX',
verbose_name="操作系統(tǒng)")
# 此服務(wù)器與哪一個(gè)app關(guān)聯(lián)
app = models.ForeignKey(App,
related_name='ra_server',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name='應(yīng)用服務(wù)')
# 保存在此服務(wù)器正在運(yùn)行的app的最近發(fā)布單
main_release = models.ForeignKey(Release,
related_name='ra_server_main',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name='主發(fā)布單')
# 保存在此服務(wù)器正在運(yùn)行的app的次新發(fā)布單稿壁,主要用于回滾,bifang只支持最近一次回滾歉备,不支持無(wú)限回滾
back_release = models.ForeignKey(Release,
related_name='ra_server_back',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name='備份發(fā)布單')
class Meta:
db_table = 'Server'
# 一個(gè)服務(wù)器上傅是,部署多個(gè)應(yīng)用,保證Ip加port的唯一性蕾羊。
unique_together = ('ip', 'port')
verbose_name = 'Server服務(wù)器'
verbose_name_plural = 'Server服務(wù)器'
- unique_together喧笔,聯(lián)合約束,這樣龟再,在同一個(gè)服務(wù)器上书闸,就可以部署多個(gè)app應(yīng)用。
- main_release 和back_release 利凑,主要用來(lái)顯示服務(wù)器最新應(yīng)用浆劲,以及回滾發(fā)布單嫌术。在每次部署時(shí),都會(huì)影響這兩個(gè)字段牌借。但只是停啟服務(wù)時(shí)度气,則不會(huì)更新這兩個(gè)字段。
八膨报,release_models
from django.db import models
from .base_models import BaseModel
from .app_models import App
from .env_models import Env
# 發(fā)布單狀態(tài)蚯嫌,為了能動(dòng)態(tài)管理,我覺(jué)得加個(gè)單獨(dú)的表丙躏,有必要。
class ReleaseStatus(BaseModel):
# ['Create', 'Building', 'BuildFailed', 'Build', 'Ready', 'Ongoing', 'Success', 'Failed']
# ['創(chuàng)建', '編譯中', '編譯失敗', '編譯成功', '準(zhǔn)備部署', '部署中', '部署成功', '部署失敗']
status_value = models.CharField(max_length=1024, blank=True, verbose_name="狀態(tài)值")
class Meta:
db_table = 'ReleaseStatus'
verbose_name = 'ReleaseStatus發(fā)布單狀態(tài)'
verbose_name_plural = 'ReleaseStatus發(fā)布單狀態(tài)'
# 發(fā)布單束凑, bifang部署平臺(tái)中的靈魂和紐帶
# 各種配置經(jīng)由它作融合晒旅,各種動(dòng)態(tài)數(shù)據(jù)經(jīng)由它啟動(dòng)發(fā)散
class Release(BaseModel):
# app關(guān)聯(lián)
app = models.ForeignKey(App,
related_name='ra_release',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="應(yīng)用")
# 環(huán)境關(guān)聯(lián),這個(gè)在新建和編譯發(fā)布單過(guò)程中汪诉,是沒(méi)有數(shù)據(jù)的废恋,在環(huán)境流轉(zhuǎn)之后才有
env = models.ForeignKey(Env,
related_name="ra_release",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="環(huán)境")
# 如果一個(gè)發(fā)布單部署了多次,或是分批在服務(wù)器部署扒寄,就有這個(gè)記錄的必要性了鱼鼓。
deploy_no = models.IntegerField(blank=True,
null=True,
default=0,
verbose_name="部署次數(shù)")
# 自定義需要部署的git分支
git_branch = models.CharField(max_length=255,
blank=True,
null=True)
# pipeline_id和pipeline_url在編譯軟件包的過(guò)程中生成,用于獲取編譯狀態(tài)及定位編譯輸出
pipeline_id = models.IntegerField(default=0)
pipeline_url = models.URLField(default='http://www.demo.com')
# 這個(gè)部署腳本该编,在git代碼中的一般會(huì)有自己獨(dú)立的目錄迄本,而在制品倉(cāng)庫(kù)時(shí),可能就有軟件包并列在同一個(gè)目錄了课竣,清晰嘉赎。
deploy_script_url = models.URLField(default=None,
blank=True,
null=True,
verbose_name="部署腳本路徑")
# 這里生成軟件包之后,直接記錄url軟件包路徑于樟,這樣在部署腳本中公条,就可以直接使用wget下載了。
# 為什么不使用salt迂曲?其實(shí)也行靶橱,但wget不是更穩(wěn)定和簡(jiǎn)單么?
zip_package_url = models.URLField(default=None,
blank=True,
null=True,
verbose_name="壓縮制品庫(kù)路徑")
# 記錄各種狀態(tài)用于前端顯示
deploy_status = models.ForeignKey(ReleaseStatus,
related_name='ra_release',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="發(fā)布單狀態(tài)")
class Meta:
db_table = 'Release'
verbose_name = 'Release發(fā)布單'
verbose_name_plural = 'Release發(fā)布單'
- ReleaseStatus表路捧,用于定義發(fā)布單的狀態(tài)关霸。用外表定義,比普通的字段鬓长,更有統(tǒng)一性谒拴。
- Release發(fā)布單數(shù)據(jù)表,需和app涉波,env關(guān)聯(lián)英上。但其狀態(tài)隨時(shí)間的變化炭序,則放到后面的ReleaseHistory表中。這樣就可以在時(shí)間上串聯(lián)起操作歷史苍日,而在空間上唯一定位當(dāng)前發(fā)布單惭聂。這對(duì)于我之前的設(shè)計(jì)來(lái)說(shuō),是個(gè)改進(jìn)(唯一尷尬的是相恃,這種ReleaseHistory數(shù)據(jù)表辜纲,本身要不是記錄操作歷史?:))---更新拦耐,服務(wù)器的操作歷史耕腾,記錄在ServerHistory數(shù)據(jù)表中,完美解決杀糯。
- git_branch 和git_commit扫俺,用于追蹤此個(gè)發(fā)布單 的代碼分支。
- deploy_no 用于跟蹤此發(fā)布單在不同環(huán)境的部署日志固翰。
- salt_path和nginx_url 用于定位此app組件的制品庫(kù)路徑狼纬。此路徑下,一般會(huì)有兩個(gè)文件骂际,一個(gè)制品包(war, zip...)疗琉,另一個(gè)部署腳本。salt會(huì)調(diào)用這個(gè)部署腳本歉铝,操作制品包的部署全過(guò)程盈简。這種集成的東東,對(duì)于之前的部署來(lái)說(shuō)太示,也是一個(gè)新思路送火。
九,history_models
from django.db import models
from .base_models import BaseModel
from .env_models import Env
from .release_models import Release, ReleaseStatus
from .server_models import Server
# 用于在發(fā)布單的部署代碼層次內(nèi)的記錄先匪,是部署新代碼种吸,還是回滾老代碼?
# 用于發(fā)布單歷史
DEPLOY_CHOICES = (
('deploy', '部署'),
('rollback', '回滾'),
)
# 用于大的方向呀非,是在部署代碼坚俗,還是單純?cè)诰S護(hù)服務(wù)器啟停?
# 用于服務(wù)器操作歷史岸裙,區(qū)分發(fā)布單和服務(wù)器操作歷史是有意義的猖败,維護(hù)不一樣,但也可以一追到底
OP_CHOICES = (
('deploy', '部署'),
('maintenance', '啟停維護(hù)'),
)
# 在服務(wù)器上操作的第一步作記錄降允,后期根據(jù)需要恩闻,有可能作維護(hù)改動(dòng)
# 它主要要能包括所有deploy過(guò)程中的action_list列表項(xiàng)目
ACTION_CHOICES = (
('fetch', '獲取軟件'),
('stop', '停止'),
('stop_status', '停止?fàn)顟B(tài)檢測(cè)'),
('deploy', '部署'),
('rollback', '回滾'),
('start', '啟動(dòng)'),
('start_status', '啟動(dòng)狀態(tài)檢測(cè)'),
('health_check', '服務(wù)健康檢測(cè)'),
)
# 發(fā)布單歷史,記錄發(fā)布單的生命周期剧董,新建幢尚,編譯破停,流轉(zhuǎn),部署尉剩,回滾部署
class ReleaseHistory(BaseModel):
release = models.ForeignKey(Release,
related_name='ra_release_history',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name='發(fā)布單')
env = models.ForeignKey(Env,
related_name="ra_release_history",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="環(huán)境")
deploy_status = models.ForeignKey(ReleaseStatus,
related_name='ra_release_history',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="發(fā)布單狀態(tài)")
deploy_type = models.CharField(max_length=255,
choices=DEPLOY_CHOICES,
blank=True,
null=True,
verbose_name="部署類型")
log = models.TextField(verbose_name="日志內(nèi)容")
class Meta:
db_table = 'ReleaseHistory'
verbose_name = 'ReleaseHistory發(fā)布單歷史'
verbose_name_plural = 'ReleaseHistory發(fā)布單歷史'
# 服務(wù)器變更歷史真慢,記錄服務(wù)器上的部署,停止理茎,回滾
class ServerHistory(BaseModel):
server = models.ForeignKey(Server,
related_name='ra_server_history',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name='服務(wù)器')
release = models.ForeignKey(Release,
related_name='ra_server_history',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name='發(fā)布單')
env = models.ForeignKey(Env,
related_name="ra_server_history",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="環(huán)境")
op_type = models.CharField(max_length=255,
choices=OP_CHOICES,
blank=True,
null=True,
verbose_name="操作類型")
action_type = models.CharField(max_length=255,
choices=ACTION_CHOICES,
blank=True,
null=True,
verbose_name="服務(wù)器操作類型")
log = models.TextField(verbose_name="日志內(nèi)容")
class Meta:
db_table = 'ServerHistory'
verbose_name = 'ServerHistory服務(wù)器歷史'
verbose_name_plural = 'ServerHistory服務(wù)器歷史'
- 維護(hù)兩個(gè)歷史表黑界,一個(gè)記錄發(fā)布單歷史,一個(gè)記錄服務(wù)器歷史皂林。待完善朗鸠。
十,permission_models
from django.db import models
from .base_models import BaseModel
from django.contrib.auth.models import User
from .app_models import App
# 創(chuàng)建編譯發(fā)布單础倍, 環(huán)境流轉(zhuǎn)童社,部署發(fā)布單三個(gè)大的權(quán)限,
# bifang暫不支持基于各個(gè)具體環(huán)境的細(xì)致權(quán)限
# 大家可以自己基于書(shū)上教授的技能著隆,自行實(shí)現(xiàn)
class Action(BaseModel):
action_id = models.IntegerField(unique=True, verbose_name="權(quán)限序號(hào)")
class Meta:
db_table = 'Action'
verbose_name = 'Action權(quán)限'
verbose_name_plural = 'Action權(quán)限'
# 具體的權(quán)限數(shù)據(jù)表
# 如果要獲取某個(gè)服務(wù)的所有權(quán)限,或是某一應(yīng)用的指定權(quán)限的用戶列表呀癣,都是OK的美浦。
class Permission(BaseModel):
# 權(quán)限與app應(yīng)用級(jí)別關(guān)聯(lián)
app = models.ForeignKey(App,
related_name="ra_permission",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="App應(yīng)用")
# 權(quán)限與具體的權(quán)限動(dòng)作(創(chuàng)建編譯,環(huán)境流轉(zhuǎn)项栏,部署發(fā)布)關(guān)聯(lián)
action = models.ForeignKey(Action,
related_name="ra_permission",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="操作權(quán)限")
# 權(quán)限關(guān)聯(lián)到用戶
pm_user = models.ForeignKey(User,
related_name="ra_permission",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="權(quán)限用戶")
class Meta:
db_table = 'Permission'
verbose_name = 'Permission應(yīng)用權(quán)限'
verbose_name_plural = 'Permission應(yīng)用權(quán)限'
- casbin感覺(jué)一般般浦辨,可能不太適用于我這種情況,或是動(dòng)靜開(kāi)大沼沈,直接手?jǐn)]流酬。
- 將權(quán)限分為三個(gè),一個(gè)是新建編譯權(quán)限列另,一個(gè)為環(huán)境流轉(zhuǎn)權(quán)限芽腾,一個(gè)為部署各個(gè)環(huán)境的權(quán)限(不再區(qū)分),自己的東東自己負(fù)責(zé)页衙。
- 能編輯權(quán)限的摊滔,只有三種用戶,一個(gè)是系統(tǒng)admin店乐,一種是project的創(chuàng)建用戶艰躺,一個(gè)是app的創(chuàng)建用戶。
- 小而美的權(quán)限眨八,也不過(guò)如彼腺兴。
十一,mock數(shù)據(jù)
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import Group
from cmdb.models import *
import string
import random
import time
import datetime
from django.contrib.auth import get_user_model
User = get_user_model()
username = 'admin'
group_name = 'admin'
# 自定義命令廉侧,用于建立測(cè)試數(shù)據(jù)页响,很多ORM語(yǔ)句會(huì)使用
class Command(BaseCommand):
help = 'create test data for BiFang back server.'
def add_arguments(self, parser):
self.stdout.write(self.style.SUCCESS('沒(méi)有額外參數(shù)篓足,新建全部模擬測(cè)試數(shù)據(jù),刪除所有舊記錄'))
def handle(self, *args, **options):
self.add_user()
self.add_git()
self.add_salt()
self.add_env()
self.add_project()
self.add_app()
self.add_server()
self.add_release_status()
self.add_release()
self.add_action()
self.add_permission()
self.stdout.write(self.style.SUCCESS('數(shù)據(jù)重建完成拘泞,一把梭哈~~~'))
# raise CommandError('Ok纷纫!')
# 新建一個(gè)用戶
def add_user(self):
User.objects.all().delete()
Group.objects.all().delete()
print('delete all user and group data')
User.objects.create_user(username='Dylan', email='user@demo.com', password="password")
User.objects.create_user(username='Tyler', email='user@demo.com', password="password")
User.objects.create_user(username='Kyle', email='user@demo.com', password="password")
User.objects.create_user(username='Dakota', email='user@demo.com', password="password")
User.objects.create_user(username='Marcus', email='user@demo.com', password="password")
User.objects.create_user(username='Samantha', email='user@demo.com', password="password")
User.objects.create_user(username='Kayla', email='user@demo.com', password="password")
User.objects.create_user(username='Sydney', email='user@demo.com', password="password")
User.objects.create_user(username='Courtney', email='user@demo.com', password="password")
User.objects.create_user(username='Mariah', email='user@demo.com', password="password")
User.objects.create_user(username='tom', email='user@demo.com', password="password")
User.objects.create_user(username='mary', email='user@demo.com', password="password")
admin = User.objects.create_superuser(username, 'admin@demon.com', 'password')
root = User.objects.create_superuser('root', 'root@demon.com', 'password')
admin_group = Group.objects.create(name=group_name)
Group.objects.create(name='test')
Group.objects.create(name='dev')
Group.objects.create(name='operate')
admin_users = [admin, root]
admin_group.user_set.set(admin_users)
self.stdout.write('用戶和用戶組重建完成。')
# 新建一個(gè)Git倉(cāng)庫(kù)
def add_git(self):
GitTb.objects.all().delete()
print('delete all GitTb data')
create_user = User.objects.get(username=username)
GitTb.objects.create(name='MainGit',
description='主要git庫(kù)',
create_user=create_user,
git_url='http://192.168.1.211:8180',
git_token='RbCcuLssPekyVgy24Nui')
self.stdout.write('GitTb重建完成陪腌。')
# 新建一個(gè)SaltApi
def add_salt(self):
SaltTb.objects.all().delete()
print('delete all SaltTb data')
create_user = User.objects.get(username=username)
SaltTb.objects.create(name='MainSalt',
description='主要SaltApi',
create_user=create_user,
salt_url='https://192.168.1.211:8000',
salt_user='saltapi',
salt_pwd='saltapipwd',
eauth='pam',
trust_host=True)
self.stdout.write('SaltTb重建完成辱魁。')
# 新建一個(gè)環(huán)境
def add_env(self):
Env.objects.all().delete()
print('delete all Env data')
create_user = User.objects.get(username=username)
salt = SaltTb.objects.order_by('?').first()
env_list = ['dev', 'prd']
for index, env in enumerate(env_list):
Env.objects.create(name=env,
description=env,
create_user=create_user,
env_id=index,
salt=salt)
self.stdout.write('Env重建完成。')
# 新建demo項(xiàng)目
def add_project(self):
Project.objects.all().delete()
print('delete all Project data')
create_user = User.objects.get(username=username)
project_name_list = ['User', 'Service', 'Store', 'Card', 'Support']
project_cn_name_list = ['用戶管理', '服務(wù)', '庫(kù)存', '購(gòu)物車', '客服']
for project_name, project_cn_name in zip(project_name_list, project_cn_name_list):
Project.objects.create(name=project_name,
description=project_name,
cn_name=project_cn_name,
create_user=create_user,
project_id=random.randint(1000, 10000))
self.stdout.write('Project重建完成诗鸭。')
# 新建demo應(yīng)用
def add_app(self):
App.objects.all().delete()
print('delete all App data')
create_user = User.objects.get(username=username)
app_name_list = ['User-Login', 'Service-724', 'Store-Address', 'Card-Adjust', 'Support-Admin', 'go-demo']
app_cn_name_list = ['用戶登陸', '全天服務(wù)', '庫(kù)存地址', '購(gòu)物車調(diào)配', '客服后管', '畢方演示go示例']
for app_name, app_cn_name in zip(app_name_list, app_cn_name_list):
git = GitTb.objects.order_by('?').first()
project = Project.objects.order_by('?').first()
App.objects.create(name=app_name,
description=app_name,
cn_name=app_cn_name,
create_user=create_user,
app_id=random.randint(10000, 100000),
git=git,
git_app_id=1,
git_trigger_token='559fbd3381bc39100811bd00e499a7',
project=project,
build_script='script/build.sh',
deploy_script='script/deploy.sh',
zip_package_name='go-demo.tar.gz',
service_port=9090,
service_username='sky',
service_group='operate')
self.stdout.write('App重建完成染簇。')
# 新建server服務(wù)器
def add_server(self):
Server.objects.all().delete()
print('delete all Server data')
create_user = User.objects.get(username=username)
for number in range(100):
ip = '192.168.1.{}'.format(number)
app = App.objects.order_by('?').first()
Server.objects.create(name=ip,
description=ip,
create_user=create_user,
ip=ip,
port=random.randint(10000, 100000),
app=app,
system_type=random.choice(['WINDOWS', 'LINUX']))
self.stdout.write('Server重建完成。')
# 新建發(fā)布單狀態(tài)
def add_release_status(self):
ReleaseStatus.objects.all().delete()
print('delete all ReleaseStatus data')
create_user = User.objects.get(username=username)
status_list = ['Create', 'Building', 'BuildFailed', 'Build', 'Ready', 'Ongoing', 'Success', 'Failed']
status_value_list = ['創(chuàng)建', '編譯中', '編譯失敗', '編譯成功', '準(zhǔn)備部署', '部署中', '部署成功', '部署失敗']
for status_name, status_value_name in zip(status_list, status_value_list):
ReleaseStatus.objects.create(name=status_name,
description=status_name,
create_user=create_user,
status_value=status_value_name)
self.stdout.write('ReleaseStatus重建完成强岸。')
# 新建demo發(fā)布單
def add_release(self):
Release.objects.all().delete()
print('delete all Release data')
create_user = User.objects.get(username=username)
for number in range(100):
app = App.objects.order_by('?').first()
env = Env.objects.order_by('?').first()
deploy_status = ReleaseStatus.objects.order_by('?').first()
random_letter = ''.join(random.sample(string.ascii_letters, 2))
name = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f") + random_letter.upper()
Release.objects.create(name=name,
description=name,
create_user=create_user,
app=app,
env=env,
git_branch='master',
pipeline_id=0,
pipeline_url='http://www.demo.com',
deploy_script_url='http://192.168.1.213:8080/a/b/bifang.sh',
zip_package_url='http://192.168.1.213:8080/a/b/go-demo.zip',
deploy_status=deploy_status)
self.stdout.write('Release重建完成锻弓。')
# 新建權(quán)限
def add_action(self):
Action.objects.all().delete()
print('delete all Action data')
create_user = User.objects.get(username=username)
Action.objects.create(name='Create',
description='創(chuàng)建編譯權(quán)限',
create_user=create_user,
action_id=100)
Action.objects.create(name='Env',
description='環(huán)境權(quán)限',
create_user=create_user,
action_id=1000)
Action.objects.create(name='Deploy',
description='部署權(quán)限',
create_user=create_user,
action_id=10000)
self.stdout.write('Action重建完成。')
# 新建demo應(yīng)用權(quán)限用戶表
def add_permission(self):
Permission.objects.all().delete()
print('delete all Permission data')
create_user = User.objects.get(username=username)
for number in range(5):
app = App.objects.order_by('?').first()
action = Action.objects.order_by('?').first()
pm_user = User.objects.order_by('?').first()
name = '{}-{}-{}'.format(app.name, action.name, pm_user.username)
Permission.objects.create(name=name,
description=name,
create_user=create_user,
app=app,
action=action,
pm_user=pm_user)
self.stdout.write('Permission重建完成蝌箍。')
- 使用
(venv) D:\Code\bifang\bifangback>python manage.py mockdb
自定義命令青灼,可把這些mock數(shù)據(jù)導(dǎo)入數(shù)據(jù)庫(kù)。 - 這里也使用了不同django orm和Python隨機(jī)選擇的技巧妓盲。