一、背景:
应用程序采用的是微服务架构,底座,子应用,工具互不干扰,独立运行,因此需要升级模块需要经历复杂的手续。
以底座为例子,首先需要去CI服务器拿包,再调用升级服务器接口,创建修改应用的接口,以post请求方式,请求体很长
创建或修改应用后,需要再次调用上传升级包的接口,上传升级包,还需要计算文件MD5.每次需要用windows的CMD命令执行,在升级子应用就需要重复一遍以上内容。
CertUtil -hashfile "C:\path\tofile\example.txt" MD5
再是发布版本接口,这个接口相对简单,发布完成后,需要从用户视角调用升级相关接口,查看升级连接返回的信息是否正确,
升级服务器只有开发提供的postman配置文件,打开导入即用。
简单来说这个流程不算复杂,但是重复性太强了,不够自动化,因此考虑设计一套简单的自动化升级
二、从CI执行机拿包
1、utils.py文件
先按照手动升级流程思路来确保自动化脚本编写思路,首先考虑的是从服务器拿包,定义一个pkg文件夹用来存放包
先写一个简单的工具,创建一个utils.py文件,里面存放一些方法用来处理和升级服务器不挂钩的功能,比如从服务器拿包和计算文件md5值
utils.py文件代码如下
def get_pkg(app,file_save_path,branch="develop"):
'''
从服务器下载文件
:param app: 下载底座还是子应用包
:param branch:分支,默认develop,也可以选择master
:return:
'''
url = f'{download_url}/{branch}/{app}/{app}.zip'
print(f"url:{url}\napp:{app}\nbranch:{branch}")
res =requests.get(url=url,stream=True)
if res.status_code == 200:
total_size = int(res.headers.get('content-length', 0))
downloaded_size = 0
with open(file_save_path,"wb") as f:
for chunk in res.iter_content(chunk_size=1024):
downloaded_size += len(chunk)
f.write(chunk)
progress = (downloaded_size / total_size) * 100
# 打印下载进度
print(f"\r下载进度: {progress:.2f}%", end="")
print("文件下载成功")
return True
else:
print("文件下载失败,状态码:", res.status_code)
return False
def calculate_md5(file_path):
'''
计算文件md5值
:param file_path:
:return:
'''
hash_md5 =hashlib.md5()
with open(file_path,"rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
#iter函数是Python的内置函数,它可以接受两个参数。第一个参数是一个可调用对象(在这里是我们的lambda表达式),
# 第二个参数是一个哨兵值。iter会重复调用这个可调用对象,直到它返回的值等于哨兵值。
# b"":这是一个空的字节字符串,它在这里作为哨兵值。当f.read(4096)到达文件末尾时,
# 它会返回一个空的字节字符串b"",这时iter函数就会停止迭代。
hash_md5.update(chunk)
return hash_md5.hexdigest()
if __name__ == '__main__':
print(calculate_md5(r'D:\Users\User\Desktop\CI\pkg\base.zip'))
get_pkg("base",r'D:\Users\User\Desktop\CI\pkg\base.zip')
2、config.py配置文件
用来存放配置文件数据,解API耦合
config.py代码:
HOST_UPGRADE = "10.10.10.10:40"
HOST_APP_INFO = "10.10.10.10:41"
download_url="http://10.10.10.110:9999"
json_data = {
"base": {
"app_name": "base",
"description": "base",
"author": "base",
"app_type": "uninstall",
"label": "tool",
"is_internal": False,
"version": "5.0.4 BUILD 20240118082647",
},
"app2": {
"app_name": "app2",
"description": "app2",
"author": "app2",
"app_type": "uninstall",
"label": "tool",
"is_internal": True,
"version": "5.0.6 BUILD 20240118083318",
},
"app1": {
"app_name": "app1",
"description": "app1",
"author": "app1",
"app_type": "uninstall",
"label": "tool",
"is_internal": True,
"version": "5.0.11 BUILD 20240118083318",
}
form_data ={
"app_name":"app",
"version":"xxx",
"pre_version":None,
"md5":"md5",
"type":"full_upgrade",
}
三、升级服务器API
升级服务器API模块代码,主要是针对三个升级服务器接口
1、api.py文件
import requests
from upgreade.config import HOST_UPGRADE, json_data, form_data, HOST_APP_INFO
from utils import calculate_md5
class UpgradeApi():
#创建或修改应用版本接口url
url = "http://"+HOST_UPGRADE+"/version"
@classmethod
def upgrade_acheck_base(cls,app,version):
'''
创建或修改应用版本接口
:param app:
:param version:
:return:
'''
data =json_data[app]
data["version"] =version
print(f"data:{data}\nversion:{version}")
return requests.post(url=cls.url,json=data)
@classmethod
def upload_pkg(cls,app,version,file_path):
'''
上传升级包接口
:param app:
:param version:
:param file:
:return:
'''
url = "http://"+HOST_UPGRADE+"/manager/pkg"
data = form_data
if app == "acheck-base":
app = "base"
data["app_name"]=app
data["version"] =version
data["md5"]=calculate_md5(file_path)
print(f"data:{data}")
with open(file_path,"rb") as f:
files = {'file': (file_path, f)} # 'file'是服务器端接收文件的字段名
res =requests.post(url=url,data=form_data,files=files)
return res
@classmethod
def public_version(cls,app,version):
'''
发布版本接口
:param app:
:param version:
:return:
'''
url = "http://"+HOST_UPGRADE+"/manager/version"
if app == "acheck-base":
app = "base"
date={
"app_name":app,
"version":version
}
return requests.put(url=url,json=date)
class GetUpgradeInfo():
'''
用户视角获取升级服务器相关接口
'''
@staticmethod
def getlist():
url = "http://"+HOST_APP_INFO+"/list"
res =requests.get(url=url)
print(res.json()["data"])
@classmethod
def getdownloadurl(cls,app):
url = "http://"+HOST_APP_INFO+"/list"
params={
"app_name":app,
}
res = requests.get(url=url)
print(res.json())
if __name__ == '__main__':
# res=UpgradeApi.upgrade_acheck_base("5.0.7 BUILD 20240118082647")
# print(res.json())
GetUpgradeInfo.getdownloadurl("base")
四、自动化升级主函数
框架雏形就有了 ,这时候就可以写一个主函数,串联起全部功能
1、main.py文件
from upgreade.api import UpgradeApi
from utils import get_pkg
class Upgrade():
@classmethod
def upgrade(cls,app,file_path,version):
'''
发布升级主函数
:param app: 需要升级的子应用
:param file_path:下载文件路径和上传升级路径
:param version:
:return:
'''
#首先需要下载包
print(f"app:{app}")
res =get_pkg(app,file_path)
if res :
print("下载成功")
#创建或修改应用版本
if app == "base":
UpgradeApi.upgrade_base(version)
elif app == "app2":
UpgradeApi.upgrade_app2(version)
elif app == "app1":
UpgradeApi.upgrade_app1(version)
else:
print("app输入异常")
#上传升级包
res =UpgradeApi.upload_pkg(app,version,file_path)
print(res.json())
if res.json()["message"] == "success":
print("上传升级包成功")
else:
print("升级包上传失败")
break
#发布版本
res =UpgradeApi.public_version(app,version)
print(f"发布{res.json()}")
if res.json()["message"] == "success":
print("发布成功")
else:
print("发布失败")
if __name__ == '__main__':
Upgrade.upgrade("base", r'D:\Users\User\Desktop\CI\pkg\base.zip',"5.0.7 BUILD 20240118082647")
五、运行效果及后言
1、运行效果
app:base
url:http://10.10.10.110:9999/develop/base/base.zip
app:acheck-base
branch:develop
下载进度: 100.00%文件下载成功
下载成功
{'code': 0, 'details': '', 'message': 'success'}
升级包上传成功
发布{'code': 0, 'data': {}, 'message': 'success'}
发布成功
2、后言
整体框架思路设计就是这样,如果后续有新的工具加入,只要在config添加相关信息,也可以直接用来自动升级,脚本设计的比较粗糙,先凑合着用,感兴趣的同学可以将print调试代码,搞成日志形式。后续如果有需要会考虑把整个框架打包,可以提供给其他同事使用。
所谓的自动化就是简化测试工作中,所有需要经常重复的流程,变成自动化执行,自动化执行可以提高工作效率。