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