找回密码
 立即注册
首页 业界区 安全 Python 实现Metersphere平台API调用

Python 实现Metersphere平台API调用

厥轧匠 2025-5-31 23:53:56
实践环境

Python 3.9.13
安装依赖包
  1. pip install pycryptodome
  2. pip install requests
复制代码
Metersphere v2.0.12
代码实现
  1. # -*- coding:utf-8 -*-
  2. import base64
  3. import os
  4. import time
  5. import uuid
  6. import json
  7. import requests
  8. from Crypto.Cipher import AES
  9. from Crypto.Util.Padding import pad
  10. # Metersphere平台-个人信息-API Keys-Access Key
  11. METER_SPHERE_ACCESS_KEY = os.environ.get('METER_SPHERE_ACCESS_KEY', 'vTiFyYFTfVAZfbRc')
  12. # Metersphere平台-个人信息-API Keys-Secret Key
  13. METER_SPHERE_SECRET_KEY = os.environ.get('METER_SPHERE_SECRET_KEY', 'N4iKP6cqT8zJLfcx')
  14. METER_SPHERE_HOST = os.environ.get('METER_SPHERE_HOST', '192.168.88.135')
  15. METER_SPHERE_PORT = os.environ.get('METER_SPHERE_PORT', '8081')
  16. METER_SPHERE_PROTOCOL = os.environ.get('METER_SPHERE_PROTOCOL', 'http')
  17. class MeterSphereCli(object):
  18.     def __init__(self):
  19.         
  20.         self.session = requests.session()
  21.         self.set_headers(METER_SPHERE_ACCESS_KEY, METER_SPHERE_SECRET_KEY)
  22.         self.host = METER_SPHERE_HOST
  23.         self.port = METER_SPHERE_PORT
  24.         self.protocol = METER_SPHERE_PROTOCOL
  25.    
  26.     def aes_encrypt(self, text, secret_key, iv: bytes = None):
  27.         cipher = AES.new(secret_key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
  28.         encrypted = cipher.encrypt(pad(text.encode('utf-8'), AES.block_size))
  29.         
  30.         # 通过aes加密后,再base64加密
  31.         b_encrypted = base64.b64encode(encrypted)
  32.         return b_encrypted
  33.    
  34.    
  35.     def set_headers(self, access_key, secret_key):
  36.         time_stamp = int(round(time.time() * 1000))
  37.         combox_key = access_key + '|' + str(uuid.uuid4()) + '|' + str(time_stamp)
  38.         signature = self.aes_encrypt(combox_key, secret_key, access_key)
  39.         header = {'Content-Type': 'application/json', 'ACCEPT': 'application/json', 'accessKey': access_key,
  40.                   'signature': signature.decode('UTF-8')}
  41.         self.session.headers.update(header)
  42.         
  43.     def get_workspaces(self, search_keyword=None, return_when_keyword_matched=True):
  44.         '''获取用户工作空间列表
  45.         @:param search_keyword 搜索关键词,仅支持 名称搜索
  46.         '''
  47.         
  48.         result = []
  49.         
  50.         url = f'{self.protocol}://{self.host}:{self.port}/track/workspace/list/userworkspace'
  51.         res = self.session.get(url)
  52.         if res.status_code != 200:
  53.             print'获取用户工作空间列表出错, 服务器返回:%s' % res.text)
  54.             return result
  55.         
  56.         res = res.json()
  57.         if res.get('success') != True:
  58.             print'获取用户工作空间列表出错, 服务器返回:%s' % res.text)
  59.             return result
  60.         
  61.         if search_keyword:
  62.             for item in res.get('data', []):
  63.                 workspace_id = item.get('id')
  64.                 name = item.get('name')
  65.                 if search_keyword in name:
  66.                     result.append({'id': workspace_id, 'name': name})
  67.                     if return_when_keyword_matched:
  68.                         return result
  69.         else:
  70.             for item in res.get('data', []):
  71.                 workspace_id = item.get('id')
  72.                 name = item.get('name')
  73.                 result.append({'id': workspace_id, 'name': name})
  74.         return result
  75.    
  76.     def get_workspace_projects(self, workspace_id, search_keyword=None, return_when_keyword_matched=True):
  77.         '''获取工作空间关联的项目
  78.         @:param search_keyword 搜索关键词,仅支持 名称搜索
  79.         '''
  80.         
  81.         result = []
  82.         
  83.         url = f'{self.protocol}://{self.host}:{self.port}/track/project/list/related'
  84.         req_body = {'workspaceId': workspace_id}
  85.         res = self.session.post(url, json=req_body)
  86.         if res.status_code != 200:
  87.             print'获取工作空间关联的项目列表出错, 服务器返回:%s' % res.text)
  88.             return result
  89.         
  90.         res = res.json()
  91.         if res.get('success') != True:
  92.             print'获取工作空间关联的项目列表出错, 服务器返回:%s' % res.text)
  93.             return result
  94.         
  95.         if search_keyword:
  96.             for item in res.get('data', []):
  97.                 project_id = item.get('id')
  98.                 name = item.get('name')
  99.                 if search_keyword in name:
  100.                     result.append({'id': project_id, 'name': name})
  101.                     if return_when_keyword_matched:
  102.                         return result
  103.         else:
  104.             for item in res.get('data', []):
  105.                 project_id = item.get('id')
  106.                 name = item.get('name')
  107.                 result.append({'id': project_id, 'name': name})
  108.         return result
  109.    
  110.    
  111.     def get_project_envs(self, project_id, search_keyword=None, return_when_keyword_matched=True):
  112.         '''获取项目环境列表
  113.         @:param search_keyword 支持环境名称、主机+端口 模糊搜索
  114.         '''
  115.         
  116.         result = []
  117.         
  118.         url = f'{self.protocol}://{self.host}:{self.port}/track/environment/list/{project_id}'
  119.         res = self.session.get(url)
  120.         if res.status_code != 200:
  121.             print'获取项目环境列表出错, 服务器返回:%s' % res.text)
  122.             return result
  123.         
  124.         res = res.json()
  125.         if res.get('success') != True:
  126.             print'获取项目环境列表出错, 服务器返回:%s' % res.text)
  127.             return result
  128.         
  129.         if search_keyword:
  130.             for item in res.get('data', []):
  131.                 env_id = item.get('id')
  132.                 name = item.get('name')
  133.                 env_config = item.get('config').strip()
  134.                 if env_config:
  135.                     env_config = json.loads(env_config)
  136.                     http_config = env_config.get('httpConfig', {})
  137.                     condition = http_config.get('conditions', [{}])[0]
  138.                     protocol = condition.get('protocol')
  139.                     socket = condition.get('socket')
  140.                     if search_keyword in name or search_keyword in socket:
  141.                         result.append({'id': env_id, 'name': name, 'protocol': protocol, 'host_port': socket})
  142.                         if return_when_keyword_matched:
  143.                             return result
  144.         else:
  145.             for item in res.get('data', []):
  146.                 env_id = item.get('id')
  147.                 name = item.get('name')
  148.                 env_config = item.get('config').strip()
  149.                 if env_config:
  150.                     env_config = json.loads(env_config)
  151.                     http_config = env_config.get('httpConfig', {})
  152.                     condition = http_config.get('conditions', [{}])[0]
  153.                     protocol = condition.get('protocol')
  154.                     socket = condition.get('socket')
  155.                     result.append({'id': env_id, 'name': name, 'protocol': protocol, 'host_port': socket})
  156.         return result
  157.    
  158.     def get_project_testplans(self, project_id, page_no=1, page_size=10, search_conditions={}):
  159.         '''获取项目计划列表
  160.         @param page: search_conditions {'name': {'value': 'test_plan_name' , 'operator': 'like'}}
  161.         '''
  162.         
  163.         result = []
  164.         url = f'{self.protocol}://{self.host}:{self.port}/track/test/plan/list/{page_no}/{page_size}'
  165.         combine = {}
  166.         if 'name' in search_conditions:
  167.             search_condition = search_conditions.get('name')
  168.             combine['name'] = {
  169.                 "operator": search_condition.get('operator'),
  170.                 "value": search_condition.get('value')
  171.             }
  172.         
  173.         req_body = {
  174.             "components": [{
  175.                 "key": "name",
  176.                 "name": "MsTableSearchInput",
  177.                 "label": "commons.name",
  178.                 "operator": {
  179.                     "value": "like",
  180.                     "options": [{
  181.                         "label": "commons.adv_search.operators.like",
  182.                         "value": "like"
  183.                     }, {
  184.                         "label": "commons.adv_search.operators.not_like",
  185.                         "value": "not like"
  186.                     }]
  187.                 }
  188.             }, {
  189.                 "key": "updateTime",
  190.                 "name": "MsTableSearchDateTimePicker",
  191.                 "label": "commons.update_time",
  192.                 "operator": {
  193.                     "options": [{
  194.                         "label": "commons.adv_search.operators.between",
  195.                         "value": "between"
  196.                     }, {
  197.                         "label": "commons.adv_search.operators.gt",
  198.                         "value": "gt"
  199.                     }, {
  200.                         "label": "commons.adv_search.operators.lt",
  201.                         "value": "lt"
  202.                     }]
  203.                 }
  204.             }, {
  205.                 "key": "createTime",
  206.                 "name": "MsTableSearchDateTimePicker",
  207.                 "label": "commons.create_time",
  208.                 "operator": {
  209.                     "options": [{
  210.                         "label": "commons.adv_search.operators.between",
  211.                         "value": "between"
  212.                     }, {
  213.                         "label": "commons.adv_search.operators.gt",
  214.                         "value": "gt"
  215.                     }, {
  216.                         "label": "commons.adv_search.operators.lt",
  217.                         "value": "lt"
  218.                     }]
  219.                 }
  220.             }, {
  221.                 "key": "moduleIds",
  222.                 "name": "MsTableSearchNodeTree",
  223.                 "label": "test_track.case.module",
  224.                 "operator": {
  225.                     "value": "in",
  226.                     "options": [{
  227.                         "label": "commons.adv_search.operators.in",
  228.                         "value": "in"
  229.                     }, {
  230.                         "label": "commons.adv_search.operators.not_in",
  231.                         "value": "not in"
  232.                     }]
  233.                 },
  234.                 "options": {
  235.                     "url": "/plan/node/list",
  236.                     "type": "POST",
  237.                     "params": {}
  238.                 }
  239.             }, {
  240.                 "key": "principal",
  241.                 "name": "MsTableSearchSelect",
  242.                 "label": "test_track.plan.plan_principal",
  243.                 "operator": {
  244.                     "options": [{
  245.                         "label": "commons.adv_search.operators.in",
  246.                         "value": "in"
  247.                     }, {
  248.                         "label": "commons.adv_search.operators.not_in",
  249.                         "value": "not in"
  250.                     }, {
  251.                         "label": "commons.adv_search.operators.current_user",
  252.                         "value": "current user"
  253.                     }]
  254.                 },
  255.                 "options": {
  256.                     "url": "/user/project/member/list",
  257.                     "labelKey": "name",
  258.                     "valueKey": "id"
  259.                 },
  260.                 "props": {
  261.                     "multiple": True
  262.                 }
  263.             }, {
  264.                 "key": "status",
  265.                 "name": "MsTableSearchSelect",
  266.                 "label": "test_track.plan.plan_status",
  267.                 "operator": {
  268.                     "options": [{
  269.                         "label": "commons.adv_search.operators.in",
  270.                         "value": "in"
  271.                     }, {
  272.                         "label": "commons.adv_search.operators.not_in",
  273.                         "value": "not in"
  274.                     }]
  275.                 },
  276.                 "options": [{
  277.                     "label": "test_track.plan.plan_status_prepare",
  278.                     "value": "Prepare"
  279.                 }, {
  280.                     "label": "test_track.plan.plan_status_running",
  281.                     "value": "Underway"
  282.                 }, {
  283.                     "label": "test_track.plan.plan_status_completed",
  284.                     "value": "Completed"
  285.                 }, {
  286.                     "label": "test_track.plan.plan_status_finished",
  287.                     "value": "Finished"
  288.                 }, {
  289.                     "label": "test_track.plan.plan_status_archived",
  290.                     "value": "Archived"
  291.                 }],
  292.                 "props": {
  293.                     "multiple": True
  294.                 }
  295.             }, {
  296.                 "key": "stage",
  297.                 "name": "MsTableSearchSelect",
  298.                 "label": "test_track.plan.plan_stage",
  299.                 "operator": {
  300.                     "options": [{
  301.                         "label": "commons.adv_search.operators.in",
  302.                         "value": "in"
  303.                     }, {
  304.                         "label": "commons.adv_search.operators.not_in",
  305.                         "value": "not in"
  306.                     }]
  307.                 },
  308.                 "options": [{
  309.                     "text": "冒烟测试",
  310.                     "value": "smoke",
  311.                     "system": True
  312.                 }, {
  313.                     "text": "系统测试",
  314.                     "value": "system",
  315.                     "system": True
  316.                 }, {
  317.                     "text": "回归测试",
  318.                     "value": "regression",
  319.                     "system": True
  320.                 }],
  321.                 "props": {
  322.                     "multiple": True
  323.                 }
  324.             }, {
  325.                 "key": "tags",
  326.                 "name": "MsTableSearchInput",
  327.                 "label": "commons.tag",
  328.                 "operator": {
  329.                     "value": "like",
  330.                     "options": [{
  331.                         "label": "commons.adv_search.operators.like",
  332.                         "value": "like"
  333.                     }, {
  334.                         "label": "commons.adv_search.operators.not_like",
  335.                         "value": "not like"
  336.                     }]
  337.                 }
  338.             }, {
  339.                 "key": "followPeople",
  340.                 "name": "MsTableSearchSelect",
  341.                 "label": "commons.follow_people",
  342.                 "operator": {
  343.                     "options": [{
  344.                         "label": "commons.adv_search.operators.in",
  345.                         "value": "in"
  346.                     }, {
  347.                         "label": "commons.adv_search.operators.current_user",
  348.                         "value": "current user"
  349.                     }]
  350.                 },
  351.                 "options": {
  352.                     "url": "/user/ws/current/member/list",
  353.                     "labelKey": "name",
  354.                     "valueKey": "id"
  355.                 },
  356.                 "props": {
  357.                     "multiple": True
  358.                 }
  359.             }, {
  360.                 "key": "actualStartTime",
  361.                 "name": "MsTableSearchDateTimePicker",
  362.                 "label": "test_track.plan.actual_start_time",
  363.                 "operator": {
  364.                     "options": [{
  365.                         "label": "commons.adv_search.operators.between",
  366.                         "value": "between"
  367.                     }, {
  368.                         "label": "commons.adv_search.operators.gt",
  369.                         "value": "gt"
  370.                     }, {
  371.                         "label": "commons.adv_search.operators.ge",
  372.                         "value": "ge"
  373.                     }, {
  374.                         "label": "commons.adv_search.operators.lt",
  375.                         "value": "lt"
  376.                     }, {
  377.                         "label": "commons.adv_search.operators.le",
  378.                         "value": "le"
  379.                     }]
  380.                 }
  381.             }, {
  382.                 "key": "actualEndTime",
  383.                 "name": "MsTableSearchDateTimePicker",
  384.                 "label": "test_track.plan.actual_end_time",
  385.                 "operator": {
  386.                     "options": [{
  387.                         "label": "commons.adv_search.operators.between",
  388.                         "value": "between"
  389.                     }, {
  390.                         "label": "commons.adv_search.operators.gt",
  391.                         "value": "gt"
  392.                     }, {
  393.                         "label": "commons.adv_search.operators.ge",
  394.                         "value": "ge"
  395.                     }, {
  396.                         "label": "commons.adv_search.operators.lt",
  397.                         "value": "lt"
  398.                     }, {
  399.                         "label": "commons.adv_search.operators.le",
  400.                         "value": "le"
  401.                     }]
  402.                 }
  403.             }, {
  404.                 "key": "planStartTime",
  405.                 "name": "MsTableSearchDateTimePicker",
  406.                 "label": "test_track.plan.planned_start_time",
  407.                 "operator": {
  408.                     "options": [{
  409.                         "label": "commons.adv_search.operators.between",
  410.                         "value": "between"
  411.                     }, {
  412.                         "label": "commons.adv_search.operators.gt",
  413.                         "value": "gt"
  414.                     }, {
  415.                         "label": "commons.adv_search.operators.ge",
  416.                         "value": "ge"
  417.                     }, {
  418.                         "label": "commons.adv_search.operators.lt",
  419.                         "value": "lt"
  420.                     }, {
  421.                         "label": "commons.adv_search.operators.le",
  422.                         "value": "le"
  423.                     }]
  424.                 }
  425.             }, {
  426.                 "key": "planEndTime",
  427.                 "name": "MsTableSearchDateTimePicker",
  428.                 "label": "test_track.plan.planned_end_time",
  429.                 "operator": {
  430.                     "options": [{
  431.                         "label": "commons.adv_search.operators.between",
  432.                         "value": "between"
  433.                     }, {
  434.                         "label": "commons.adv_search.operators.gt",
  435.                         "value": "gt"
  436.                     }, {
  437.                         "label": "commons.adv_search.operators.ge",
  438.                         "value": "ge"
  439.                     }, {
  440.                         "label": "commons.adv_search.operators.lt",
  441.                         "value": "lt"
  442.                     }, {
  443.                         "label": "commons.adv_search.operators.le",
  444.                         "value": "le"
  445.                     }]
  446.                 }
  447.             }],
  448.             "orders": [],
  449.             "nodeIds": [],
  450.             "projectId": project_id,
  451.             "selectAll": False,
  452.             "unSelectIds": [],
  453.             "combine": combine
  454.         }
  455.         res = self.session.post(url, json=req_body)
  456.         if res.status_code != 200:
  457.             print'获取项目计划列表出错, 服务器返回:%s' % res.text)
  458.             return result
  459.         
  460.         res = res.json()
  461.         if res.get('success') != True:
  462.             print'获取项目计划列表出错, 服务器返回:%s' % res.text)
  463.             return result
  464.         
  465.         data = res.get('data', {})
  466.         item_count = data.get('itemCount', 0)
  467.         
  468.         while item_count > 0:
  469.             for test_plan in data.get('listObject', []):
  470.                 id = test_plan.get('id')
  471.                 name = test_plan.get('name')
  472.                 project_id = test_plan.get('projectId')
  473.                 result.append({'id':id, 'name': name, 'project_id': project_id})
  474.             
  475.             item_count -= page_size
  476.             if item_count > 0:
  477.                 page_no += 1
  478.                 url = f'{self.protocol}://{self.host}:{self.port}/track/test/plan/list/{page_no}/{page_size}'
  479.                
  480.                 res = self.session.post(url, json=req_body)
  481.                 if res.status_code != 200:
  482.                     print'获取项目计划列表出错, 服务器返回:%s' % res.text)
  483.                     return result
  484.                
  485.                 res = res.json()
  486.                 if res.get('success') != True:
  487.                     print'获取项目计划列表出错, 服务器返回:%s' % res.text)
  488.                     return result
  489.                 data = res.get('data', {})
  490.         return result
  491.    
  492.     def get_resource_pool(self, search_keyword=None, return_when_keyword_matched=True):
  493.         ''' 获取运行资源池
  494.         @:param search_keyword 搜索关键词,支持名称模糊搜索
  495.         '''
  496.         
  497.         result = []
  498.         
  499.         url = f'{self.protocol}://{self.host}:{self.port}/track/testresourcepool/list/quota/valid'
  500.         res = self.session.get(url)
  501.         if res.status_code != 200:
  502.             print'获取资源池失败,服务器返回:%s' % res.text)
  503.             return result
  504.         
  505.         res = res.json()
  506.         if res.get('success') != True:
  507.             print'获取资源池失败,服务器返回:%s' % res.text)
  508.             return result
  509.         
  510.         if search_keyword:
  511.             for item in res.get('data'):
  512.                 pool_id = item.get('id')
  513.                 name = item.get('name')
  514.                 if search_keyword in name :
  515.                     result.append({'id': pool_id, 'name': name})
  516.                     if return_when_keyword_matched:
  517.                         return result
  518.         else:
  519.             for item in res.get('data'):
  520.                 pool_id = item.get('id')
  521.                 name = item.get('name')
  522.                 result.append({'id':pool_id, 'name':name})
  523.         return result
  524.         
  525.         
  526.     def call_testplan(self, resource_pool_id, poject_id, test_plan_id, env_id):
  527.         ''' 运行测试计划 '''
  528.         
  529.         result = {}
  530.         
  531.         url = f'{self.protocol}://{self.host}:{self.port}/track/test/plan/run'
  532.         req_body = {
  533.             "mode": "serial",
  534.             "reportType": "iddReport",
  535.             "onSampleError": False,
  536.             "runWithinResourcePool": False,
  537.             "resourcePoolId": resource_pool_id,
  538.             "envMap": {
  539.                 poject_id: env_id
  540.             },
  541.             "testPlanId": test_plan_id,
  542.             "projectId": poject_id,
  543.             "userId": "admin",
  544.             "triggerMode": "MANUAL",
  545.             "environmentType": "JSON",
  546.             "environmentGroupId": "",
  547.             "testPlanDefaultEnvMap": {
  548.             },
  549.             "requestOriginator": "TEST_PLAN",
  550.             "retryEnable": False,
  551.             "retryNum": 1,
  552.             "browser": "CHROME",
  553.             "headlessEnabled": False,
  554.             "executionWay": "RUN"
  555.         }
  556.         
  557.         res = self.session.post(url, json=req_body)
  558.         if res.status_code != 200:
  559.             print'执行测试计划失败,服务器返回:%s' % res.text)
  560.             return result
  561.         
  562.         res = res.json()
  563.         if res.get('success') != True:
  564.             print'执行测试计划失败,服务器返回:%s' % res.text)
  565.             return result
  566.         result = {'report_id': res.get('data')}
  567.         return result
  568. ms_cli = MeterSphereCli()
  569. if __name__ == '__main__':
  570.     testplan_id = None
  571.     project_id = None
  572.     project_env_id = None
  573.     res_pool_id = None
  574.     #
  575.     res = ms_cli.get_workspaces('默认工作空间')
  576.     if res:
  577.         workspace_id = res[0].get('id')
  578.         projects = ms_cli.get_workspace_projects(workspace_id, search_keyword='默认项目')
  579.         if projects:
  580.             project = projects[0]
  581.             project_id = project.get('id')
  582.             testplans = ms_cli.get_project_testplans(project_id, 1, page_size=10, search_conditions={'name': {'value': '全局前置准备', 'operator':'like'}})
  583.             if testplans:
  584.                 testplan = testplans[0]
  585.                 testplan_id = testplan.get('id')
  586.     #
  587.     if project_id:
  588.         project_envs = ms_cli.get_project_envs(project_id, '测试环境')
  589.         if project_envs:
  590.             project_env = project_envs[0]
  591.             project_env_id = project_env.get('id')
  592.     pools = ms_cli.get_resource_pool('LOCAL')
  593.     if pools:
  594.         pool = pools[0]
  595.         res_pool_id = pool.get('id')
  596.     if res_pool_id and project_id and testplan_id and project_env_id:
  597.         res = ms_cli.call_testplan(res_pool_id, project_id, testplan_id, project_env_id)
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册