Python中Sonar客户端问题
Python中Sonar客户端问题
一、问题
遗留容器中有个Python脚本中用到Sonar客户端,容器重建后Python以及Sonar模块依赖丢失。容器中安装Python 3.11后,Python脚本一直提示ModuleNotFoundError: No module named ‘sonarqube’
1 | ModuleNotFoundError: No module named 'sonarqube' |
系统环境如下:
- Debian 12
- Python 3.11
- Pip 23.0.1
1 | lsb_release -aNo LSB modules are available.Distributor ID: DebianDescription: Debian GNU/Linux 12 (bookworm)Release: 12Codename: bookworm |
1 | lsb_release -aNo LSB modules are available.Distributor ID: DebianDescription: Debian GNU/Linux 12 (bookworm)Release: 12Codename: bookworm |
1 | python --versionPython 3.11.2pip --versionpip 23.0.1 from /usr/lib/python3/dist-packages/pip (python 3.11) |
1 | python --versionPython 3.11.2pip --versionpip 23.0.1 from /usr/lib/python3/dist-packages/pip (python 3.11) |
二、解决
Python脚本的功能是部分Sonar客户端代码如下:
1 | import sysimport smtplibfrom email.mime.text import MIMETextfrom email.mime.multipart import MIMEMultipartfrom collections import defaultdictfrom sonarqube import SonarQubeClientfrom time import sleepimport jsondef sendmail(subject, msg, toaddrs, fromaddr, smtpserver, password): mail_msg = MIMEMultipart() mail_msg['Subject'] = subject mail_msg['From'] = fromaddr mail_msg['To'] = ','.join(toaddrs) mail_msg.attach(MIMEText(msg, 'html', 'utf-8')) try: s = smtplib.SMTP(host=smtpserver,port=587) s.starttls() s.login(fromaddr, password) # 登录邮箱 s.sendmail(fromaddr, toaddrs, mail_msg.as_string()) # 发送邮件 s.quit() print("send successful!") except Exception as e: print(e) print("Failed to send ")def getSonarqubeInfo(component=None, sonar:SonarQubeClient=None): component_data = sonar.measures.get_component_with_specified_measures( component=component, fields="metrics,periods", metricKeys=""" code_smells,bugs,coverage,line_coverage,branch_coverage,duplicated_lines_density,ncloc, security_rating,reliability_rating,vulnerabilities,comment_lines_density, ncloc_language_distribution,alert_status,sqale_rating,new_coverage,new_line_coverage,new_branch_coverage """ ) result_dict = {} for info_dict in component_data["component"]["measures"]: if "periods" in info_dict and info_dict["periods"]: # 如果 periods 存在且不为空,取第一个元素的 value period_value = info_dict["periods"][0].get("value") # 如果字符串可以转换为float的话,格式化period_value为小数点后两位 period_value = "{:.1f}".format(float(period_value)) else: period_value = info_dict.get("value") result_dict[info_dict["metric"]] = info_dict.get("value") or period_value # print(result_dict) return result_dictdef main(): url = "http://sonar.xxx.com" #分支名,不存在时使用master分支 if len(sys.argv) < 4: print("Usage: python sonar-report.py <branch> <receivers> <report_task_file> <jenkins_build_url>") sys.exit(1) # 分支名 branch = sys.argv[1] # 邮件接收人列表,逗号分隔 receivers = sys.argv[2] # 这个分支是sonar-maven-plugin生成的元数据文件,默认名字为report-task.txt report_task_file = sys.argv[3] # Jenkins构建任务地址 jenkins_build_url = sys.argv[4] sonar = SonarQubeClient(sonarqube_url=url, token="xxx") properties_dict = {} with open(report_task_file, 'r') as file: lines = file.readlines() for line in lines: line = line.strip() # 去除每行的首尾空白字符 if line and not line.startswith('#'): # 跳过空行和注释行(假设注释以#开头) key, value = line.split('=', 1) # 以等号分割,最多分割一次 properties_dict[key.strip()] = value.strip() project = properties_dict.get("projectKey") ceTaskUrl = properties_dict.get("ceTaskUrl") success = False while not success: x = sonar.session.get(url=ceTaskUrl) resp = json.loads(x.text) success = resp["task"]["status"] == "SUCCESS" if not success: print("sonar任务未完成,等待1s, sonar任务状态:" + resp["task"]["status"]) sleep(1) project_url = "{}/dashboard?id={}".format(url, project) user_email = 'xxx@yyy' sonarqube_data = getSonarqubeInfo(component=project, sonar=sonar) # 创建一个 defaultdict,指定默认值为 None 或其他值 default_dict = defaultdict(lambda: None, sonarqube_data) html_text = """<!DOCTYPE html><html lang="en"><head> <title></title> <meta charset="utf-8"></head><body><div class="page" style="margin-left: 30px"> <h3>你好</h3> <h3> 本次提交代码检查结果如下</h3> <h3> 项目名称:{project} 分支:{branch}</h3> <h4>一、总体情况</h4> <ul> <li style="font-weight:bold;"> 本次扫描代码行数: <span style="color:blue">{lines} </span>, bugs: <span style="color:red">{bugs}</span>, Vulnerabilities: <span style="color:red">{vulnerabilities}</span>, Code Smells: <span style="color:red">{code_smells}</span> </li> <li style="font-weight:bold;margin-top: 10px;"> Sonar地址: <a style="font-weight:bold;" href={project_url}>{project_url} </a> </li> <li style="font-weight:bold;margin-top: 10px;"> Jenkins地址: <a style="font-weight:bold;" href={jenkins_build_url}>{jenkins_build_url} </a> </li> </ul> <h4>二、信息详情</h4> <ul> <li style="font-weight:bold;"> 综合等级: {sqale_rating} </li> <li style="font-weight:bold;"> 总体覆盖率: {coverage}% </li> <li style="font-weight:bold;"> 行覆盖率: {line_coverage}% </li> <li style="font-weight:bold;"> 分支覆盖率: {branch_coverage}% </li> <li style="font-weight:bold;"> 新代码覆盖率: {new_coverage}% </li> <li style="font-weight:bold;"> 新代码的行覆盖率: {new_line_coverage}% </li> <li style="font-weight:bold;"> 新代码的分支覆盖率: {new_branch_coverage}% </li> </ul></div></body></html>""".format(project_url=project_url, user_mail=user_email, project=project, lines=default_dict["ncloc"], bugs=default_dict["bugs"], vulnerabilities=default_dict["vulnerabilities"], code_smells=default_dict["code_smells"], sqale_rating=default_dict["sqale_rating"], coverage=default_dict["coverage"], line_coverage=default_dict["line_coverage"], branch_coverage=default_dict["branch_coverage"], branch=branch, new_coverage=default_dict["new_coverage"], new_line_coverage=default_dict["new_line_coverage"], new_branch_coverage=default_dict["new_branch_coverage"], jenkins_build_url=jenkins_build_url ) fromaddr = "xxx@yyy" smtpserver = "smtp.xxx.com" toaddrs = receivers.split(",") subject = "单测报告" password = "xxx" msg = html_text sendmail(subject, msg, toaddrs, fromaddr, smtpserver, password)if __name__ == '__main__': main() |
1 | import sysimport smtplibfrom email.mime.text import MIMETextfrom email.mime.multipart import MIMEMultipartfrom collections import defaultdictfrom sonarqube import SonarQubeClientfrom time import sleepimport jsondef sendmail(subject, msg, toaddrs, fromaddr, smtpserver, password): mail_msg = MIMEMultipart() mail_msg['Subject'] = subject mail_msg['From'] = fromaddr mail_msg['To'] = ','.join(toaddrs) mail_msg.attach(MIMEText(msg, 'html', 'utf-8')) try: s = smtplib.SMTP(host=smtpserver,port=587) s.starttls() s.login(fromaddr, password) # 登录邮箱 s.sendmail(fromaddr, toaddrs, mail_msg.as_string()) # 发送邮件 s.quit() print("send successful!") except Exception as e: print(e) print("Failed to send ")def getSonarqubeInfo(component=None, sonar:SonarQubeClient=None): component_data = sonar.measures.get_component_with_specified_measures( component=component, fields="metrics,periods", metricKeys=""" code_smells,bugs,coverage,line_coverage,branch_coverage,duplicated_lines_density,ncloc, security_rating,reliability_rating,vulnerabilities,comment_lines_density, ncloc_language_distribution,alert_status,sqale_rating,new_coverage,new_line_coverage,new_branch_coverage """ ) result_dict = {} for info_dict in component_data["component"]["measures"]: if "periods" in info_dict and info_dict["periods"]: # 如果 periods 存在且不为空,取第一个元素的 value period_value = info_dict["periods"][0].get("value") # 如果字符串可以转换为float的话,格式化period_value为小数点后两位 period_value = "{:.1f}".format(float(period_value)) else: period_value = info_dict.get("value") result_dict[info_dict["metric"]] = info_dict.get("value") or period_value # print(result_dict) return result_dictdef main(): url = "http://sonar.xxx.com" #分支名,不存在时使用master分支 if len(sys.argv) < 4: print("Usage: python sonar-report.py <branch> <receivers> <report_task_file> <jenkins_build_url>") sys.exit(1) # 分支名 branch = sys.argv[1] # 邮件接收人列表,逗号分隔 receivers = sys.argv[2] # 这个分支是sonar-maven-plugin生成的元数据文件,默认名字为report-task.txt report_task_file = sys.argv[3] # Jenkins构建任务地址 jenkins_build_url = sys.argv[4] sonar = SonarQubeClient(sonarqube_url=url, token="xxx") properties_dict = {} with open(report_task_file, 'r') as file: lines = file.readlines() for line in lines: line = line.strip() # 去除每行的首尾空白字符 if line and not line.startswith('#'): # 跳过空行和注释行(假设注释以#开头) key, value = line.split('=', 1) # 以等号分割,最多分割一次 properties_dict[key.strip()] = value.strip() project = properties_dict.get("projectKey") ceTaskUrl = properties_dict.get("ceTaskUrl") success = False while not success: x = sonar.session.get(url=ceTaskUrl) resp = json.loads(x.text) success = resp["task"]["status"] == "SUCCESS" if not success: print("sonar任务未完成,等待1s, sonar任务状态:" + resp["task"]["status"]) sleep(1) project_url = "{}/dashboard?id={}".format(url, project) user_email = 'xxx@yyy' sonarqube_data = getSonarqubeInfo(component=project, sonar=sonar) # 创建一个 defaultdict,指定默认值为 None 或其他值 default_dict = defaultdict(lambda: None, sonarqube_data) html_text = """<!DOCTYPE html><html lang="en"><head> <title></title> <meta charset="utf-8"></head><body><div class="page" style="margin-left: 30px"> <h3>你好</h3> <h3> 本次提交代码检查结果如下</h3> <h3> 项目名称:{project} 分支:{branch}</h3> <h4>一、总体情况</h4> <ul> <li style="font-weight:bold;"> 本次扫描代码行数: <span style="color:blue">{lines} </span>, bugs: <span style="color:red">{bugs}</span>, Vulnerabilities: <span style="color:red">{vulnerabilities}</span>, Code Smells: <span style="color:red">{code_smells}</span> </li> <li style="font-weight:bold;margin-top: 10px;"> Sonar地址: <a style="font-weight:bold;" href={project_url}>{project_url} </a> </li> <li style="font-weight:bold;margin-top: 10px;"> Jenkins地址: <a style="font-weight:bold;" href={jenkins_build_url}>{jenkins_build_url} </a> </li> </ul> <h4>二、信息详情</h4> <ul> <li style="font-weight:bold;"> 综合等级: {sqale_rating} </li> <li style="font-weight:bold;"> 总体覆盖率: {coverage}% </li> <li style="font-weight:bold;"> 行覆盖率: {line_coverage}% </li> <li style="font-weight:bold;"> 分支覆盖率: {branch_coverage}% </li> <li style="font-weight:bold;"> 新代码覆盖率: {new_coverage}% </li> <li style="font-weight:bold;"> 新代码的行覆盖率: {new_line_coverage}% </li> <li style="font-weight:bold;"> 新代码的分支覆盖率: {new_branch_coverage}% </li> </ul></div></body></html>""".format(project_url=project_url, user_mail=user_email, project=project, lines=default_dict["ncloc"], bugs=default_dict["bugs"], vulnerabilities=default_dict["vulnerabilities"], code_smells=default_dict["code_smells"], sqale_rating=default_dict["sqale_rating"], coverage=default_dict["coverage"], line_coverage=default_dict["line_coverage"], branch_coverage=default_dict["branch_coverage"], branch=branch, new_coverage=default_dict["new_coverage"], new_line_coverage=default_dict["new_line_coverage"], new_branch_coverage=default_dict["new_branch_coverage"], jenkins_build_url=jenkins_build_url ) fromaddr = "xxx@yyy" smtpserver = "smtp.xxx.com" toaddrs = receivers.split(",") subject = "单测报告" password = "xxx" msg = html_text sendmail(subject, msg, toaddrs, fromaddr, smtpserver, password)if __name__ == '__main__': main() |
尝试安装下面Sonar客户端都没有解决
- sonar-client 0.0.5
- sonarqube-api 1.3.1
最后通过安装下面客户端解决
- python-sonarqube-api 2.0.5
1 | # 安装模块,建议通过venv环境安装,这里全局安装仅仅用于演示 pip install python-sonarqube-api --break-system-packages # 验证 python -c "import sonarqube; print(sonarqube.__version__)" 2.0.5 # 下面没有异常输出即可 python -c "from sonarqube import SonarQubeClient" |
1 | # 安装模块,建议通过venv环境安装,这里全局安装仅仅用于演示 pip install python-sonarqube-api --break-system-packages # 验证 python -c "import sonarqube; print(sonarqube.__version__)" 2.0.5 # 下面没有异常输出即可 python -c "from sonarqube import SonarQubeClient" |
三、补充
1 | # 本地或者流水线中发起sonar扫描请求mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.7.0.1746:sonar \ -Dsonar.host.url=${xxx} \ -Dsonar.projectKey=${xxx} \ -Dsonar.login=${xxx} \ -Dsonar.projectName=${xxx} |
1 | # 本地或者流水线中发起sonar扫描请求mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.7.0.1746:sonar \ -Dsonar.host.url=${xxx} \ -Dsonar.projectKey=${xxx} \ -Dsonar.login=${xxx} \ -Dsonar.projectName=${xxx} |
- report-task.txt内容例子
1 | projectKey=xxxserverUrl=http://sonar.xxx.comserverVersion=7.8.0.26217dashboardUrl=http://sonar.xxx.com/dashboard?id=xxxceTaskId=xxxceTaskUrl=http://sonar.xxx.com/api/ce/task?id=xxx |
1 | projectKey=xxxserverUrl=http://sonar.xxx.comserverVersion=7.8.0.26217dashboardUrl=http://sonar.xxx.com/dashboard?id=xxxceTaskId=xxxceTaskUrl=http://sonar.xxx.com/api/ce/task?id=xxx |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 石头记!