更新时间:2026-06-12 GMT+08:00
分享

使用构建任务制作SonarQube工具镜像

  1. 创建一个CodeArts Repo代码仓用于存放构建镜像脚本,可以参考新建CodeArts Repo代码仓
  2. 单击按钮在代码仓根目录中新建一个目录“.cloudbuild”,并在该目录下新建一个文件“build.yml”。
  3. 在代码仓根目录新建三个空文件,名称分别为:Dockerfile、excute_py.sh、generate_excel.py。

    此时的目录结构如下图。

  4. 更改前置步骤所获取的Dockerfile。

    1. 删除第二行的“USER build”。
    2. 从第二行开始写入如下内容:
      # Define build arguments and set default values
      # SonarQube Server version
      ARG SONARQUBE_VERSION=25.4.0.105899
      # SonarScanner CLI version
      ARG SONAR_SCANNER_VERSION=7.2.0.5079
      # JDK version required for SonarQube runtime
      ARG SONAR_JDK_VERSION=17.0.1
      # Node.js version required for SonarQube (optional, required only for JavaScript/TypeScript/CSS scanning scenarios)
      ARG SONAR_NODE_VERSION=20.19.6
      # JDK version required for compiling user code (optional, required only for Java compilation scanning scenarios)
      ARG COMPILE_JDK_VERSION=21.0.1
      # Maven version required for compiling user code (optional, required only for Java compilation scanning scenarios)
      ARG COMPILE_MAVEN_VERSION=3.9.12
      # Print versions
      RUN echo "Version of SonarQube Server: $SONARQUBE_VERSION" \
        && echo "Version of SonarScanner CLI: $SONAR_SCANNER_VERSION" \
        && echo "Version of JDK for SonarQube : $SONAR_JDK_VERSION" \
        && echo "Version of Node.js for SonarQube: $SONAR_NODE_VERSION" \
        && echo "Version of JDK for compiling code: $COMPILE_JDK_VERSION" \
        && echo "Version of Maven for compiling code: $COMPILE_MAVEN_VERSION"
      # Download tools & extract & rename
      RUN mkdir -p /usr/local/tools/sonarqubecustom
      RUN cd /usr/local/tools/sonarqubecustom \
        && curl -fSL https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-${SONARQUBE_VERSION}.zip -o sonarqube_webservice.zip \
        && unzip -q sonarqube_webservice.zip \
        && mv sonarqube-${SONARQUBE_VERSION} sonarqube_webservice \
        && rm -f sonarqube_webservice.zip
      RUN cd /usr/local/tools/sonarqubecustom \
        && curl -fSL https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux-x64.zip -o sonarqube.zip \
        && unzip -q sonarqube.zip \
        && mv sonar-scanner-${SONAR_SCANNER_VERSION}-linux-x64 sonarqube \
        && rm -f sonarqube.zip
      RUN cd /usr/local/tools/sonarqubecustom \
        && curl -fSL https://mirrors.huaweicloud.com/openjdk/${SONAR_JDK_VERSION}/openjdk-${SONAR_JDK_VERSION}_linux-x64_bin.tar.gz -o jdk.tar.gz\
        && tar -xzf jdk.tar.gz \
        && mv jdk-${SONAR_JDK_VERSION} jdk \
        && rm -f jdk.tar.gz
      # Node.js (optional, required only for JavaScript/TypeScript/CSS scanning scenarios, used in conjunction with lines 11)
      RUN cd /usr/local/tools/sonarqubecustom \
        && curl -fSL https://nodejs.org/dist/v${SONAR_NODE_VERSION}/node-v${SONAR_NODE_VERSION}-linux-x64.tar.xz -o nodejs.tar.xz \
        && tar -xf nodejs.tar.xz \
        && mv node-v${SONAR_NODE_VERSION}-linux-x64 nodejs \
        && rm -f nodejs.tar.xz
      #  Download compilation dependencies & extract & rename (optional, required only for Java compilation scanning scenarios, used in conjunction with lines 13 and 15)
      RUN cd /usr/local/ \
        && curl -fSL https://mirrors.huaweicloud.com/openjdk/${COMPILE_JDK_VERSION}/openjdk-${COMPILE_JDK_VERSION}_linux-x64_bin.tar.gz -o compile_jdk.tar.gz \
        && tar -xzf compile_jdk.tar.gz \
        && mv jdk-${COMPILE_JDK_VERSION} compile_jdk \
        && rm -f compile_jdk.tar.gz
      RUN cd /usr/local/ \
        && curl -fSL https://dlcdn.apache.org/maven/maven-3/${COMPILE_MAVEN_VERSION}/binaries/apache-maven-${COMPILE_MAVEN_VERSION}-bin.tar.gz -o maven.tar.gz \
        && tar -xzf maven.tar.gz \
        && mv apache-maven-${COMPILE_MAVEN_VERSION} maven \
        && rm -f maven.tar.gz
      # Configure compilation environment (optional, required only for Java compilation scanning scenarios, used in conjunction with lines 13 and 15)
      ENV JAVA_HOME=/usr/local/compile_jdk
      ENV MAVEN_HOME=/usr/local/maven
      ENV PATH=$MAVEN_HOME/bin:$JAVA_HOME/bin:$PATH
    3. 将上述操作更新完毕的Dockefile文件内容复制写入代码仓根目录的“Dockerfile”文件中。

  5. 将如下内容复制写入代码仓中的“.cloudbuild/build.yml”文件:

    ---
    version: 2.0
    steps:
      BUILD:
        - build_image:
            inputs:
              organization: ${ORGANIZATION}
              image_name: ${IMAGE_NAME}
              image_tag: ${IMAGE_TAG}
              dockerfile_path: ./Dockerfile
              build_args:
                - name: SONAR_USER_NAME
                  value: ${SONAR_USER_NAME}
                - name: SONAR_USER_PASSWORD
                  value: ${SONAR_USER_PASSWORD}
                - name: SONARQUBE_VERSION
                  value: ${SONARQUBE_VERSION}
                - name: SONAR_SCANNER_VERSION
                  value: ${SONAR_SCANNER_VERSION}
                - name: SONAR_JDK_VERSION
                  value: ${SONAR_JDK_VERSION}
                - name: SONAR_NODE_VERSION
                  value: ${SONAR_NODE_VERSION}
                - name: COMPILE_JDK_VERSION
                  value: ${COMPILE_JDK_VERSION}
                - name: COMPILE_MAVEN_VERSION
                  value: ${COMPILE_MAVEN_VERSION}
        - swr:
            image: ${SWR_PATH}/${ORGANIZATION}/${IMAGE_NAME}:${IMAGE_TAG}
            inputs:
              command: sh ./excute_py.sh
        - upload_artifact:
            inputs:
              path: ./output/*

  6. 将如下内容复制写入代码仓根目录的“excute_py.sh”文件:

    #!/bin/bash
    set -ex
    # Generate Excel for sonarqube rules
    # Configure yum repository
    cp -a /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
    cat >/etc/yum.repos.d/CentOS-Base.repo << 'EOF'
    [base]
    name=CentOS-$releasever - Base - mirrors.huaweicloud.com
    baseurl=https://mirrors.huaweicloud.com/centos/$releasever/os/$basearch/
    #mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
    gpgcheck=1
    gpgkey=https://mirrors.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-7
     
    #released updates 
    [updates]
    name=CentOS-$releasever - Updates - mirrors.huaweicloud.com
    baseurl=https://mirrors.huaweicloud.com/centos/$releasever/updates/$basearch/
    #mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates
    gpgcheck=1
    gpgkey=https://mirrors.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-7
     
    #additional packages that may be useful
    [extras]
    name=CentOS-$releasever - Extras - mirrors.huaweicloud.com
    baseurl=https://mirrors.huaweicloud.com/centos/$releasever/extras/$basearch/
    #mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras
    gpgcheck=1
    gpgkey=https://mirrors.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-7
     
    #additional packages that extend functionality of existing packages
    [centosplus]
    name=CentOS-$releasever - Plus - mirrors.huaweicloud.com
    baseurl=https://mirrors.huaweicloud.com/centos/$releasever/centosplus/$basearch/
    #mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus
    gpgcheck=1
    enabled=0
    gpgkey=https://mirrors.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-7
    EOF
    yum clean all
    yum makecache
    # Configure Python script execution environment
    yum install -y python3
    python3 -m pip install requests openpyxl
    yum install -y glibc-common
    localedef -c -f UTF-8 -i en_US en_US.UTF-8
    export LANG=en_US.UTF-8
    export LC_ALL=en_US.UTF-8
    # Create non-root user and start SonarQube Server
    useradd sonar && chown -R sonar:sonar /usr/local/tools/sonarqubecustom/sonarqube_webservice
    su sonar -c "/usr/local/tools/sonarqubecustom/sonarqube_webservice/bin/linux-x86-64/sonar.sh start"
    until curl -sf http://localhost:9000/api/system/status | grep -q '"status":"UP"'; do
      echo "wait to up"
      sleep 5
    done
    # Execute Python script to generate Excel
    python3 ./generate_excel.py
    

  7. 将如下内容写入代码仓根目录的“generate_excel.py”文件:

    import requests
    import json
    import uuid
    import openpyxl
    import os
    import re
    USER_NAME = os.getenv("SONAR_USER_NAME")  # SonarQube Server username
    USER_PASSWORD = os.getenv("SONAR_USER_PASSWORD")  # SonarQube Server password
    SONARQUBE_URL = "http://localhost:9000"  # SonarQube Server address
    RULE_SEARCH_API = f"{SONARQUBE_URL}/api/rules/search"
    LANGUAGE_LIST_API = f"{SONARQUBE_URL}/api/languages/list"
    GENERATE_TOKEN_API = f"{SONARQUBE_URL}/api/user_tokens/generate"
    REVOKE_TOKEN_API = f"{SONARQUBE_URL}/api/user_tokens/revoke"
    EN_EXCEL_PATH = "output/sonarqube_rules_en.xlsx"  # Excel for English interface import
    CN_EXCEL_PATH = "output/sonarqube_rules_cn.xlsx"  # Excel for Chinese interface import
    SONAR_CODEARTS_LANGUAGE_MAP = {
        "cs": "C#",
        "css": "CSS",
        "go": "GO",
        "web": "HTML",
        "java": "JAVA",
        "ts": "TYPESCRIPT",
        "js": "JAVASCRIPT",
        "python": "PYTHON",
        "php": "PHP",
        "py": "PYTHON",
        "kotlin": "KOTLIN",
        "rust": "RUST",
        "scala": "SCALA",
        "terraform": "TERRAFORM",
        "plsqlopen": "SQL"
    }
    CN_SONAR_CODEARTS_SEVERITY_MAP = {
        "BLOCKER": "致命",
        "CRITICAL": "严重",
        "MAJOR": "一般",
        "MINOR": "一般",
        "INFO": "提示"
    }
    EN_SONAR_CODEARTS_SEVERITY_MAP = {
        "BLOCKER": "critical",
        "CRITICAL": "major",
        "MAJOR": "minor",
        "MINOR": "minor",
        "INFO": "suggestion"
    }
    # CodeArts Check whitelist validation for rule names and Rule Ids
    NAME_REGEX = r'^[\u4e00-\u9fa5a-zA-Z0-9\s.\-_~\'“”‘’"\(\)#\[\]@<>*+,:!=/\\$?{}`|&;%]+$'
    name_pattern = re.compile(NAME_REGEX)
    RULE_KEY_REGEX = r'^[A-Za-z0-9_\.\-\:]+$'
    rule_key_pattern = re.compile(RULE_KEY_REGEX)
    EN_HEADERS = ["name", "nameCn", "language", "isSupportPersonal", "isSupportVersion", "isSupportIDE", "severity",
                  "criterion_type", "toolRule_name", "toolName", "good_example_cn", "good_example_en", "bad_example_cn",
                  "bad_example_en", "recommend_cn", "recommend_en", "comments_cn", "comments_en", "options"]
    CN_HEADERS = ["规则英文名称", "规则中文名称", "语言", "门禁级", "版本级", "IDE级", "告警严重级别", "规则类型",
                  "工具规则名称", "检查工具", "正确示例(中文)", "正确示例(英文)", "错误示例(中文)", "错误示例(英文)",
                  "修改建议(中文)", "修改建议(英文)", "规则描述(中文)", "规则描述(英文)", "选项"]
    # Generate a random temporary token
    def generate_sonar_token(sonar_token_name):
        try:
            # Send a POST request
            response = requests.post(
                GENERATE_TOKEN_API,
                auth=(USER_NAME, USER_PASSWORD),
                data={
                    "name": sonar_token_name
                }
            )
            if response.status_code == 200:
                try:
                    # Try to parse JSON data
                    data = response.json()
                    print("Token generation successful!")
                    return data.get("token")
                except json.JSONDecodeError as e:
                    print(f"Error parsing token generation JSON: {e}")
                    return ""
            else:
                print(f"Token generation request failed, status code: {response.status_code}")
                return ""
        except requests.exceptions.RequestException as e:
            print(f"Token generation request error: {e}")
            return ""
    # Delete the generated temporary token
    def revoke_token(sonar_token_name):
        try:
            # Send a POST request
            response = requests.post(
                REVOKE_TOKEN_API,
                auth=(USER_NAME, USER_PASSWORD),
                data={
                    "name": sonar_token_name
                }
            )
            if response.status_code != 204:
                print("Token deletion failed!")
                print(f"Token deletion request failed, status code: {response.status_code}")
        except requests.exceptions.RequestException as e:
            print(f"Token deletion request error: {e}")
        print("Token deletion successful!")
    # Get all rule information
    def get_rule_json(sonar_token, language):
        # === Initial pagination parameters ===
        page = 1
        page_size = 500  # Maximum 500 per page (SonarQube maximum)
        data = []
        rules = []
        get_whole_rule = False
        while not get_whole_rule:
            params = {
                "languages": language,  # Specify language
                "p": page,  # Current page number
                "ps": page_size  # Number of items per page
            }
            try:
                # Send a GET request
                response = requests.get(RULE_SEARCH_API, params=params, auth=(sonar_token, ""))
                if response.status_code == 200:
                    try:
                        # Try to parse JSON data
                        data = response.json()
                    except json.JSONDecodeError as e:
                        print(f"Error parsing {language} language rules JSON: {e}")
                else:
                    print(f"Getting {language} rules failed, status code: {response.status_code}")
            except requests.exceptions.RequestException as e:
                print(f"Error getting {language} rules: {e}")
            if not data.get("rules"):
                get_whole_rule = True
            rules += data.get("rules")
            page += 1
        print(f"Parsing {language} rules successful!")
        return rules
    def generate_rules_excel(is_international, rules_list, headers, output_path):
        print("Exporting rules to Excel...")
        excel = openpyxl.Workbook()
        sheet_active = excel.active
        sheet_active.title = "Sheet1"
        sheet = excel["Sheet1"]
        sheet.append(headers)
        for rule_info_dict in rules_list:
            description = ""
            recommendation = ""
            rule_key = rule_info_dict.get("key")
            rule_id = rule_key.split(":")[1]
            name = rule_info_dict.get("name")
            severity = rule_info_dict.get("severity")
            is_template = rule_info_dict.get("isTemplate")
            params = rule_info_dict.get("params")
            option = ""
            if is_template:
                rule_key = rule_key.replace(".", "_") + "_userdefine_template"
                option = option + "sonar_template_key:" + rule_id + ";"
            if params:
                for param in params:
                    option = option + param.get("key") + ":" + \
                             (param.get("defaultValue") if param.get("defaultValue") is not None else "") + ";"
            if option:
                option = option[0: len(option) - 1]
            if not rule_key_pattern.match(rule_key):
                print("SonarQube " + rule_info_dict.get("lang") + " language RuleId violation: " + rule_key)
            if not name_pattern.match(name):
                print("SonarQube " + rule_info_dict.get("lang") + " language rule name violation: " + name)
            for content in rule_info_dict.get("descriptionSections"):
                if content.get("key") == "root_cause" or content.get("key") == "default" \
                        or content.get("key") == "assess_the_problem":
                    description = description + content.get("content")
                elif content.get("key") == "introduction":
                    description = content.get("content") + description
                elif content.get("key") == "how_to_fix":
                    recommendation = recommendation + content.get("content")
            language = SONAR_CODEARTS_LANGUAGE_MAP.get(rule_info_dict.get("lang"))
            if is_international:
                information = [name, name, language if language is not None else rule_info_dict.get("lang").upper(),
                               "yes", "yes", "no", EN_SONAR_CODEARTS_SEVERITY_MAP.get(severity), "common", rule_key,
                               "sonarqubecustom", "N/A", "N/A", "N/A", "N/A", recommendation if recommendation else "N/A",
                               recommendation if recommendation else "N/A", description if description else "N/A",
                               description if description else "N/A", option]
            else:
                information = [name, name, language if language is not None else rule_info_dict.get("lang").upper(),
                               "是", "是", "否", CN_SONAR_CODEARTS_SEVERITY_MAP.get(severity), "通用类(非编程规范)", rule_key,
                               "sonarqubecustom", "无", "无", "无", "无", recommendation if recommendation else "无",
                               recommendation if recommendation else "无", description if description else "无",
                               description if description else "无", option]
            sheet.append(information)
        if not os.path.exists("output"):
            os.mkdir("output")
        excel.save(filename=output_path)
        print("Rules Excel generated successfully!")
    # Get all languages from SonarQube Server
    def get_language_list(sonar_token):
        language_list = []
        languages = []
        try:
            # Send a GET request
            response = requests.get(LANGUAGE_LIST_API, auth=(sonar_token, ""))
            if response.status_code == 200:
                try:
                    # Try to parse JSON data
                    languages = response.json().get("languages")
                    print("Language list retrieved successfully")
                except json.JSONDecodeError as e:
                    print(f"Error parsing language list JSON: {e}")
            else:
                print(f"Getting language list failed, status code: {response.status_code}")
        except requests.exceptions.RequestException as e:
            print(f"Error getting language list: {e}")
        for language in languages:
            language_list.append(language.get("key"))
        return language_list
    if __name__ == "__main__":
        token_name = str((uuid.uuid1()))
        token = generate_sonar_token(token_name)
        if not token:
            raise Exception("Token generation failed")
        sonar_languages = get_language_list(token)
        if not sonar_languages:
            raise Exception("Language list retrieval failed")
        rules = []
        for sonar_language in sonar_languages:
            rules_info = get_rule_json(token, sonar_language)
            if not rules_info:
                print(sonar_language + " language rules retrieval failed")
            rules += rules_info
        revoke_token(token_name)
        generate_rules_excel(False, rules, CN_HEADERS, CN_EXCEL_PATH)
        generate_rules_excel(True, rules, EN_HEADERS, EN_EXCEL_PATH)
    

  8. 新建并配置代码化构建任务,参考新建代码化构建任务

    注意:创建任务时不要修改build.yml文件。

  9. 单击“编辑”,选择“参数设置 > 自定义参数”。
  10. 单击“立即新建”,创建第一个参数。
  11. 单击“新建参数”,需要新建如下12个参数并设置默认值(可选的参数可以不新建),详情参见:表1

    表1 自定义参数表

    参数名

    参数描述

    默认值示例

    默认值说明

    SONAR_USER_NAME

    SonarQube Server的用户名

    admin

    从官网直接下载的SonarQube Server,登录的用户名默认为admin。

    SONAR_USER_PASSWORD

    SonarQube Server的密码

    admin

    从官网直接下载的SonarQube Server,登录的密码默认为admin。

    SONARQUBE_VERSION

    SonarQube Server的版本

    25.4.0.105899

    将版本号拼接到下载链接中,即“https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-25.4.0.105899.zip”。下载方式详见Dockerfile

    注意:需要确保该版本存在,在浏览器中打开如下链接即可进行确认,https://binaries.sonarsource.com/?prefix=Distribution/sonarqube/

    SONAR_SCANNER_VERSION

    SonarScanner CLI的版本

    7.2.0.5079

    将版本号拼接到下载链接中,即“https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-7.2.0.5079-linux-x64.zip”。下载方式详见Dockerfile

    注意:需要确保该版本存在,在浏览器中打开如下链接即可进行确认,https://binaries.sonarsource.com/?prefix=Distribution/sonar-scanner-cli/

    SONAR_JDK_VERSION

    该版本SonarQube Server运行所需的JDK版本

    17.0.1

    将版本号拼接到下载链接中,即“https://mirrors.huaweicloud.com/openjdk/17.0.1/openjdk-17.0.1_linux-x64_bin.tar.gz”。下载方式详见Dockerfile

    注意:需要确保该版本存在,在浏览器中打开如下链接即可进行确认,https://mirrors.huaweicloud.com/openjdk/

    SONAR_NODE_VERSION

    可选,SonarQube分析JavaScript/TypeScript/CSS语言所用到Node.js版本

    20.19.6

    将版本号拼接到下载链接中,即“https://nodejs.org/dist/v20.19.6/node-v20.19.6-linux-x64.tar.xz”。下载方式详见Dockerfile

    注意:需要确保该版本存在,在浏览器中打开如下链接即可进行确认,https://nodejs.org/dist/

    COMPILE_JDK_VERSION

    可选,SonarQube分析Java代码时,编译用户代码所需的JDK版本

    21.0.1

    将版本号拼接到下载链接中,即“https://mirrors.huaweicloud.com/openjdk/21.0.1/openjdk-21.0.1_linux-x64_bin.tar.gz”。下载方式详见Dockerfile

    注意:需要确保该版本存在,在浏览器中打开如下链接即可进行确认,https://mirrors.huaweicloud.com/openjdk/

    COMPILE_MAVEN_VERSION

    可选,SonarQube分析Java代码时,编译用户代码所需的Maven版本

    3.9.12

    将版本号拼接到下载链接中,即“https://dlcdn.apache.org/maven/maven-3/3.9.12/binaries/apache-maven-3.9.12-bin.tar.gz”。下载方式详见Dockerfile

    注意:需要确保该版本存在,在浏览器中打开如下链接即可进行确认,https://dlcdn.apache.org/maven/

    SWR_PATH

    用户所在局点的SWR地址

    swr.cn-north-4.myhuaweicloud.com

    可以在前置步骤中获取到的Dockerfile第一行看到。

    ORGANIZATION

    用户的SWR镜像组织

    myorganization

    在容器镜像服务所创建的组织。组织的约束与限制参考容器镜像服务的约束与限制

    IMAGE_NAME

    制作出的SonarQube镜像名称

    sonarqubecustom

    用户自定义。

    IMAGE_TAG

    制作出的SonarQube镜像Tag

    v1.0

    用户自定义。

    全部配置完后如下图:

  12. 单击“保存并执行”。

    注意:

    • 若对参数开启了“运行时设置”开关,在运行前还可以修改参数值,需再次单击“确定”开始执行。
    • 若用户设置的“IMAGE_NAME”值不是已有的镜像名称而是新建的名称,那么第一次执行会失败,原因是第一次上传的镜像默认为私有镜像,导致镜像无法拉取,如下图。

    此时需要将镜像设置为公开镜像。参考使用SWR公共镜像将镜像设置为“公开”。设置为公开镜像后重新执行一遍构建任务。

  13. 待任务执行完毕,获取“构建包下载”中的“sonarqube_rules_cn.xlsx”和“sonarqube_rules_en.xlsx”两个文件,用于导入SonarQube规则。此时在SWR中即可看到对应的镜像。

相关文档