一、SonarQube:持续代码质量检查

1.1 SonarQube:持续代码质量检查

SonarQube是一个代码质量管理工具,能对20多种编程语言源码进行代码味道(Code Smells)、Bugs 、安全漏洞方面的静态分析。SonarQube有4个版本:开源版、开发者版、企业版、数据中心版(Data Center Edition)。

https://www.sonarsource.com/plans-and-pricing/

Alt Image Text

1.2 安装sonarqube (helm)

  • https://hub.helm.sh/charts/oteemo/sonarqube
$ kuebctl create ns sonarqube

$ helm repo add oteemocharts https://oteemo.github.io/charts
"oteemocharts" has been added to your repositories

$ helm install sonarqube  oteemocharts/sonarqube -n sonarqube -f values.yaml
NAME: sonarqube
LAST DEPLOYED: Thu Jul 30 13:33:06 2020
NAMESPACE: sonarqube
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace sonarqube -l "app=sonarqube,release=sonarqube" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl port-forward $POD_NAME 8080:9000

$ kubectl port-forward svc/sonarqube-sonarqube 9000:9000 -n sonarqube 
Forwarding from 127.0.0.1:9000 -> 9000
Forwarding from [::1]:9000 -> 9000
Handling connection for 9000

admin
admin

values.yaml

  • https://github.com/helm/charts/blob/master/stable/sonarqube/values.yaml
service:
  type: NodePort
$ helm upgrade sonarqube  oteemocharts/sonarqube -n sonarqube -f values.yaml
Release "sonarqube" has been upgraded. Happy Helming!
NAME: sonarqube
LAST DEPLOYED: Fri Jul 31 14:09:20 2020
NAMESPACE: sonarqube
STATUS: deployed
REVISION: 4
NOTES:
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace sonarqube -o jsonpath="{.spec.ports[0].nodePort}" services sonarqube-sonarqube)
  export NODE_IP=$(kubectl get nodes --namespace sonarqube -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
$ kubectl get svc -n sonarqube 
NAME                            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
sonarqube-postgresql            ClusterIP   10.106.235.197   <none>        5432/TCP         22h
sonarqube-postgresql-headless   ClusterIP   None             <none>        5432/TCP         22h
sonarqube-sonarqube             NodePort    10.101.144.173   <none>        9000:32314/TCP   22h

Alt Image Text

1.3 Maven与SonarQube集成

1.3.1 Maven与SonarQube集成

<plugin> 
      <groupId>org.codehaus.mojo</groupId> 
      <artifactId>sonar—maven—plugin</artifactId> 
      <version>3.4.1.1168</version> 
 </plugin> 

执行命令

mvn clean org.sonarsource.scanner.maven:sonar-maven-plugin:3.4.1.1168:sonar -Dsonar.host.url=http://192.168.33.1:32324
#!groovy
@Library('jenkinslib@master') _

def build = new org.devops.buildtools()

pipeline {
    agent { node { label "hostmachine" }}
    parameters {
        string(name: 'srcUrl', defaultValue: 'http://192.168.33.1:30088/root/demo-maven-service.git', description: '') 
        choice(name: 'branchName', choices: 'sonarqube\nmaster', description: 'Please chose your branch')
        choice(name: 'buildType', choices: 'mvn', description: 'build tool')
        choice(name: 'buildShell', choices: 'clean package -DskipTest\n--version', description: 'build tool')
    }

    stages{
        stage('Checkout') {
            steps {
                script {
                    checkout([$class: 'GitSCM', branches: [[name: "${branchName}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "${srcUrl}"]]])
                } 
            }
        }

        stage('sonarqube test') {
            steps {
                script{
                mvnHome = tool "m2"
                sh "${mvnHome}/bin/mvn clean org.sonarsource.scanner.maven:sonar-maven-plugin:3.4.1.1168:sonar -Dsonar.host.url=http://192.168.33.1:32314"
                }
            }
        }
    }
}

sonar.host.ur参数用于指定SonarQube服务的地址这时,就可以在SonarQubeProjects中看到结果

Alt Image Text

+ /opt/maven/bin/mvn clean org.sonarsource.scanner.maven:sonar-maven-plugin:3.4.1.1168:sonar -Dsonar.host.url=http://192.168.33.1:32314
[INFO] Scanning for projects...
Downloading from central: 
...
[INFO] User cache: /home/vagrant/.sonar/cache
[INFO] SonarQube version: 8.2.0
[INFO] Default locale: "en_US", source code encoding: "UTF-8" (analysis is platform dependent)
[WARNING] SonarScanner will require Java 11 to run starting in SonarQube 8.x
[INFO] Load global settings
[INFO] Load global settings (done) | time=165ms
[INFO] Server id: EA8D9556-AXOeledPvj9r7r4MIG91
[INFO] User cache: /home/vagrant/.sonar/cache
[INFO] Load/download plugins
[INFO] Load plugins index
[INFO] Load plugins index (done) | time=62ms
...

SonarQube服务默认允许任何人执行源码分析,因此在生产环境中使用会有安全隐患。以下几步可以提高其安全性:

  • (1)设置SonarQube禁止非登录用户使用

Alt Image Text

  • (2)为用户生成Token, Jenkins只能通过TokenSonarQube集成。登录SonarQube,进入个人设置页而中的Security tab/account/security/

Alt Image Text

  • 10e7097178d25c86c1056d33024870caf5f01bc9

  • (3)在执行mvn命令时加入相应的sonar.login参数。

mvn clean org.sonarsource.scanner.maven:sonar-maven-plugin:3.4.1.1168:sonar -Dsonar.host.url=http://192.168.33.1:32314 -Dsonar.login=10e7097178d25c86c1056d33024870caf5f01bc9

1.3.2 2 Jenkins与SonarQube集成

我们将MavenSonarQube集成。这时,SonarQube对于Jenkins来说还是很明确的,Jenkins并不知道代码质量如何。本节我们将集成JenkinsSonarQube,以实现当代码 量不合格时,Jenkins pipeline失败。

具体步骤如下:

  • (1)Jenkins:安装SonarQube Scanner插件(https://plugins.jenkins.io/sonar)

  • (2)Jenkins:配置SonarQube Scanner插件,

Alt Image Text

  • (3)SonarQube:设置Webhooks。不同代码规模的源妈分析过程的耗时是不一样的。所以 当分析完成11寸由SonarQube主动通知Jenkins设置方法就是进人SonarQubeAdminstration -> Configuration -> Webhooks页加人<Jenkins的地址>/sonarqube-webhook

Alt Image Text

  • https://192.168.33.11:8080/sonarqube-webhook

<Jenkins的地址>/sonarqube-webhook/接口由 Jenkiins SonaQube插件提供

  • (4)在JerkrnsfiIe中加入SonarQubestage
stage('sonarqube test') {
            steps {
                withSonarQubeEnv('sonarqube') {
                script{
                    mvnHome = tool "m2"
                    sh "${mvnHome}/bin/mvn clean org.sonarsource.scanner.maven:sonar-maven-plugin:3.4.1.1168:sonar -Dsonar.host.url=http://192.168.33.1:32314" 
                    }
                }  
            }
        }

        stage('Quality Gate') {
            steps {
                timeout(time: 1, unit: 'HOURS') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }
  • withSonarQubeEnv是一个环境变量包装器,读取的是我们在所配置的变量。在它的闭包内,我们可以使用以下变量。

  • SONAR_HOST_URL:SonarQube服务的地址。

  • SONAR_AUTH_TOKEN: SonarQube认证所需要的Token

Alt Image Text

waitForQualityGate步骤告诉Jenkins等待SonarQube返回的分析结果。当它的 abortPipeline参数为true时,代表当质量不合格时,将pipeline的状态设置为UNSTABLE

我们同时使用了timeout包装器来设置waitForQualityGate步骤的超时时间,避免当网络出问题时,Jenkins任务一直处于等待状态。

  • (5)设置Quality Gates(质量阈值)。在SonarQube“Quality Gates”下,我们可以看到系统自带的质量,阈值。可以看出它是针对新代码的。所以,在初次及没有新代码加入的情况下,执行代码分析是不会报出构建失败的。

Alt Image Text

1.4 使用SonarQube Scanner实现代码扫描

上文中,我们是使用Maven插件实现代码扫描的,也就是利用构建工具本身提供的插件来实现。在构建工具本身不支持的情况下,我们使用SonarQube本身提供的扫描工具(Scanner)进行代码扫描。

具体步骤如下:

  • (1)在安装SonarQube Scanner:插件后,设置扫描工具自动下载并安装(推荐)。configureTools -> SonarQube Scanner)

Alt Image Text

请注意,这里的Name值与所设置的值是两码事。此处设置的是SonarScanner工具本身的名称与路径

  • (2)在代码项目根目录下放人sonar-project.properties文件sonar-scanner会读取其配置内容如下:
## must be unique in a given SonarQube instance 
sonar.projectKey=my:project
## this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1. 
sonar.projectName=My project
sonar.projectVersion=1.0

## This property is optional if Sonar.modules is set. 
sonar.sources=.

## Encoding of the source code. Default is default system encoding 
## sonar.sourceEncoding=UTE8 
  • (3)pipeline部分代码如下
script{ 
    def sonarHome = tool name: 'sonarqube4.2.0', type: 'hudson.plusinS.sonar.SonarRunnerInstallation' 
    withSomarQubeEnv('sonar') { 
        sh "${sonarHome}/bin/sonar—scanner —Dsonar.host.url=${SONAR_HOST_URL} —Dsonar.login=${SONAR_AUTH_TOKEN}"
    }
}

1.5 将分析报告推送到GitLab

如果希望对每一次代码的commit都进行分析,并将分析结果与该commit关联起来,那么SonarQubeGitLab插件就是一个不错的选择。SonarQube GitLab插件的功能就是将SonarQube 的分析结果推送到GitLab

  • (1)在SonarQube上安装GitLab插件

https://github.com/helm/charts/blob/master/stable/sonarqube/values.yaml

image:
  repository: sonarqube
  tag: 8.4.1-community
service:
  type: NodePort
plugins:
  intsalls:
    - https://github.com/gabrie-allaigre/sonar-gitlab-plugin/releases/download/4.1.0-SNAPSHOT/sonar-gitlab-plugin-4.1.0-SNAPSHOT.jar
$ helm upgrade sonarqube  oteemocharts/sonarqube -n sonarqube -f values.yaml

admin/settings?category=almintegration

Alt Image Text

gitlab part

admin/applications

  • http://localhost:32314/oauth2/callback/gitlab
  • Application ID: 706e48d74eabfb7330beaf747732182833ceb79fa43ac180e248a4b77f55cfed
  • Secret: 646b3c54b4244d0fc5ccf8cf906728c2818a60e285675186dd731b21a189c0c8

Alt Image Text

配置好sonarQube GitLab插件后,需要为sonar-scanner添加几个参数,以告诉SonarQube 将分析结果关联GitLab的相应commit上。

script{ 

    def sonarHome = tool name: 'sonarqube3.2.0', type: 'hudson.plugins.sonar.SonarRunnerInstallation' 
    def GIT_COMMIT_ID = sh( 
        script: "git rev—parse ——short=10 HEAD", 
        returnStdout: true 
)

sh "${sonarHome}/bin/sonar—scanner —Dsonar.host.url=${SONAR_HOST_URL} —Dsonar.analysis.mode=preview —Dsonar.gitlab.ref_name=master-Dsonar.gitlab.project_id=jenkins—book/sonarqube —Dsonar.projectName=jenkins—book—Dsonar.gitlab.commit_sha=${GIT_COMMIT_ID} —Dsonar.login=${SONAR_AUTH_TOKEN}" 
} 

首先通过sh步骤获取代码的commitID,然后在执行扫描时加人如下参数.

  • -Dsonar.analysis.mode:分析报告模式,值为preview,代表将结果推送到GitLab。此参数虽 然官方标注SonarQube 6.6后被废弃.
  • -Dsonar.gitlab.ref_name:分支名称。
  • -Dsonar.gitlab.project_id: GitLab对一应的项目路径。
  • Dsonar.projectName:对应SonarQube上的项目名称。
  • -Dsonar.gitlab.comit_sha:代码的commitID

SonarQube分析完成后,我们就可以在GitLab的相应Commit页面上的代码行内或commit评论区看到分析结果了

二、Allure测试报告:更美观的测试报告

2.1 Allure测试报告介绍

是不是觉得JUnit输出的测试报告不美观。 不只是JUnit很多其他编程语言的测试框架的测试报告也差不多。Allure测试报告是一个框架, 能将各种测试报告更美观地呈现出来。

2.2 集成Allure、Maven、Jenkins

接下来,我们将AllureMavens Jenkins集成。Allure与其他编程语言及构建工具的集成与 此类似。 具体步骤如下:

  • (1)安装Allure Jenkins插件,进人Jenkins的Manage Jenkins -> Global Tool Configuration - Allure Commandline页,配置Allure自动下载并 装的版本

Alt Image Text

  • (2)在pom.xml文件中加入依赖。

  • (3)在pom.xml文件中加入Allure插件(https://github.com/allure-framework/allure-maven )。

<dependency>
        <groupId>io.qameta.allure</groupId>
        <artifactId>allure-junit4</artifactId>
        <version>2.6.0</version>
</dependency>
<plugin> 
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId> 
        <version>2.19.1</version> 
        <configuration> 
        <argLine> 
             - javaagent:" ${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
        </argLine>
        <properties> 
        <property> 
        <name>listener</name> 
        <value>io.qameta.allure.junit4/AllureJunit4</value> 
      </property> 
    </properties> 
    <systemProperties> 
      <property> 
        <name>allure.results.directory</name> 
        <value>${project.build.directory}/allure-results</value> 
      </property> 
      <property>  
        <name>allure.link.issue.pattern</name> 
        <value>https://example.org/issue/{}</value> 
      </property> 
    </systemProperties> 
    </configuration> 
    <dependencies>
      <dependency> 
      <groupId>org.aspectj</groupId> 
      <artifactId>aspectjweaver</artifactId>
      <version>${aspectj.version}</version> 
      </dependency>
    </dependencies> 
  </plugin>
  <plugin>
    <groupId>io.qameta.allure</groupId> 
    <artifactId>allure-maven</artifactId> 
    <version>2.8</version> 
  </plugin>

(4)在Jenkinsfile中的post 阶段加入allure步骤。

script { 
    allure([ 
        includeProperties: false, 
        jdk: '', 
        properties: [], 
        reportBuildPolicy: 'ALWAYS', 
        results: [[path: 'target/allure-results']]
    ]) 
} 

Alt Image Text

三、代码质量平台集成(SonarQube Final Edition)

3.1 SonarQube平台简介与配置

1、一台SonarQube Server启动3个主要过程:

1.  Web服务器,供开发人员,管理人员浏览高质量的快照并配置SonarQube实例
2.  基于`Elasticsearch``Search Server``UI`进行后退搜索
3.  `Compute Engine`服务器,负责处理代码分析报告并将其保存在`SonarQube`数据库中

2、一个SonarQube数据库要存储:

1.  SonarQube实例的配置安全性插件设置等
2.  项目视图等的质量诀照

3、服务器上安装了多个SonarQube插件,可能包括语言,SCM,集成,身份验证和管理插件 4、在构建/持续集成服务器上运行一个或多个SonarScanner,以分析项目

组件组成

1、sonarqube server : 他有三个程序分别是 webserver(配置和管理sonar) searchserver(搜索结果返回给sonarUIComplateEngineserver(计算服务 将分析结果入库)。 2、sonarqube db : 数据库 存放配置。 3、sonarqube plugins: 插件增加功能。 4、sonar-scanner : 代码扫描工具可以有多个。

Alt Image Text

3.1.1 工作流程

Alt Image Text

下面的模式展示了SonarQube如何与其他ALM工具集成,以及使用SONARQUE的各种组件。

  • 开发人员在IDE中编写代码,并使用SONARLILT来运行本地分析
  • 开发人员将他们的代码推到他们最喜欢的SCM:Git,Svn,TFVC,…
  • 连续集成服务器触发自动构建,执行SONARQUE扫描器需要运行SONARQUE分析。
  • 分析报告被发送到SONARQUE服务器进行处理。
  • SONARQUE服务器在SONARQUE数据库中处理和存储分析报告结果,并将结果显示在UI中。
  • 开发人员审查、评论、挑战他们的问题,通过SONARQUE UI管理和减少他们的技术债务。
  • 管理者从分析中得到报告。
  • OPS使用API来自动配置并从SONARQUE中提取数据。
  • OPS使用JMX监控SONARQUBE服务器

3.1.2 SonarQube安装

On server

  • 运行SonarQube的唯一前提条件是在计算机上安装Java (Oracle JRE 11或OpenJDK 11)
  • 实例至少需要2GBRAM才能有效运行,而OS则需要1GB的可用RAM
  • 每个节点分配50Gb的驱动器空间
  • 安装在具有出色读写性能的硬盘驱动器上(“数据”文件夹中包含Elasticsearch索引,当服务器启动并运行时,将在该索引上进行大量的I/O)
  • SonarQube在服务器端不支持32位系统。但是,SonarQube确实在扫描仪侧支持32位系统。

支持平台

  • 运行SonarQube的唯一前提条件是在计算机上安装Java (Oracle JRE 11Open.JDK 11)

Alt Image Text

sysctl -w vm.max_ma_count=262144 
sysctl -w fs.file-max=65536
ulimit -n 65536 
ulimit -u 4096 

Install For Docker

docker run --rm -d --name sonarqube \ 
-p 9000:9000 \ 
-v ${LOCALDIR}/sonar/sonarqube_conf:/opt/sonarqube/conf \
-v ${LOCALDIR}/sonar/sonarqube_extensions:/opt/sonarqube/extensions \ 
-v ${LOCALDIR}/sonar/sonarqube_logs:/opt/sonarqube/logs \ 
-v ${LOCALDIR}/sonar/sonarqube data:/opt/sonarqube/data \ 
sonarqube:8.4.1-community 

3.1.3 服务集成

LDAPS

Alt Image Text

vim sonarqube_conf/sonar.properties

#LDAP sesttings 
#admin 

sonar.security.realm=LDAP 
ldap.url=ldap://192.168.1.200:389 
ldap.bindDn=cn=admin,dc=devops,dc=com 
ldap.bindPassword=ldap123 

## users
ldap.user.baseDn=ou=jenkins,dc=devops,dc=com 
ldap.user.request= (&(objectClass=inetOrgPerson)(cn={login})) 
ldap.user.realNameAttribute=cn 
ldap.user.emailAttribute=mail

gitlab integration

  • gitlab part

admin/applications

  • http://localhost:32314/oauth2/callback/gitlab
  • Application ID: 706e48d74eabfb7330beaf747732182833ceb79fa43ac180e248a4b77f55cfed
  • Secret: 646b3c54b4244d0fc5ccf8cf906728c2818a60e285675186dd731b21a189c0c8

Alt Image Text

  • admin/settings?category=almintegration

Alt Image Text

3.2 SonarQube扫描仪配置

https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner

3.3 SonarQube本地使用扫描仪项目分析配置

3.3.1 下载一个简单的maven项目到本地

$ git clone https://github.com/zeyangli/simple-java-maven-app
$ cd simple-java-maven-app
$ mv simple-java-maven-app demo-maven-service
$ mvn clean package

[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.mycompany.app:my-app >----------------------
[INFO] Building my-app 1.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ my-app ---
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ my-app ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/vagrant/test/simple-java-maven-app/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ my-app ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /home/vagrant/test/simple-java-maven-app/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ my-app ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/vagrant/test/simple-java-maven-app/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ my-app ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /home/vagrant/test/simple-java-maven-app/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ my-app ---
[INFO] Surefire report directory: /home/vagrant/test/simple-java-maven-app/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.mycompany.app.AppTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.075 sec

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ my-app ---
[INFO] Building jar: /home/vagrant/test/simple-java-maven-app/target/my-app-1.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.512 s
[INFO] Finished at: 2020-08-01T06:10:01Z
[INFO] ------------------------------------------------------------------------
$ tree target/
target/
├── classes
│   └── com
│       └── mycompany
│           └── app
│               └── App.class
├── maven-archiver
│   └── pom.properties
├── maven-status
│   └── maven-compiler-plugin
│       ├── compile
│          └── default-compile
│              ├── createdFiles.lst
│              └── inputFiles.lst
│       └── testCompile
│           └── default-testCompile
│               ├── createdFiles.lst
│               └── inputFiles.lst
├── my-app-1.1-SNAPSHOT.jar
├── surefire-reports
│   ├── com.mycompany.app.AppTest.txt
│   └── TEST-com.mycompany.app.AppTest.xml
└── test-classes
    └── com
        └── mycompany
            └── app
                └── AppTest.class

16 directories, 10 files

3.3.2 Run SonarScanner from the Docker image

docker run \
    --rm \
    -e SONAR_HOST_URL="http://192.168.33.1:32314" \
    -e SONAR_LOGIN="admin" \
    -e SONAR_PASSWORD="admin" \
    -v "/home/vagrant/tools/sonar:/usr/src" \
    sonarsource/sonar-scanner-cli -Dsonar.projectKey=demo-maven-service
docker run \
    --rm \
    -e SONAR_HOST_URL="http://${SONARQUBE_URL}" \
    -v "${YOUR_REPO}:/usr/src" \
    sonarsource/sonar-scanner-cli
$ docker run \
>     --rm \
>     -e SONAR_HOST_URL="http://192.168.33.1:32314" \
>     -e SONAR_LOGIN="admin" \
>     -e SONAR_PASSWORD="admin" \
>     -v "/home/vagrant/tools/sonar:/usr/src" \
>     sonarsource/sonar-scanner-cli -Dsonar.projectKey=demo-maven-service
INFO: Scanner configuration file: /opt/sonar-scanner/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE
INFO: SonarScanner 4.4.0.2170
INFO: Java 11.0.6 AdoptOpenJDK (64-bit)
INFO: Linux 3.10.0-957.12.2.el7.x86_64 amd64
INFO: User cache: /opt/sonar-scanner/.sonar/cache
INFO: Scanner configuration file: /opt/sonar-scanner/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE
INFO: Analyzing on SonarQube server 8.4.1
INFO: Default locale: "en_US", source code encoding: "UTF-8" (analysis is platform dependent)
INFO: Load global settings
INFO: Load global settings (done) | time=376ms
INFO: Server id: EA8D9556-AXOeledPvj9r7r4MIG91
INFO: User cache: /opt/sonar-scanner/.sonar/cache
INFO: Load/download plugins
INFO: Load plugins index
INFO: Load plugins index (done) | time=341ms
INFO: Load/download plugins (done) | time=8919ms
INFO: Process project properties
INFO: Process project properties (done) | time=1ms
INFO: Execute project builders
INFO: Execute project builders (done) | time=7ms
INFO: Project key: demo-maven-service
INFO: Base dir: /usr/src
INFO: Working dir: /usr/src/.scannerwork
INFO: Load project settings for component key: 'demo-maven-service'
INFO: Load quality profiles
INFO: Load quality profiles (done) | time=754ms
INFO: Load active rules
INFO: Load active rules (done) | time=6033ms
WARN: SCM provider autodetection failed. Please use "sonar.scm.provider" to define SCM of your project, or disable the SCM Sensor in the project settings.
INFO: Indexing files...
INFO: Project configuration:
INFO: 1 file indexed
INFO: ------------- Run sensors on module demo-maven-service
INFO: Load metrics repository
INFO: Load metrics repository (done) | time=365ms
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/opt/sonar-scanner/.sonar/cache/52f5340dd05620cd162e2b9a45a57124/sonar-javascript-plugin.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
INFO: Sensor SonarCSS Rules [cssfamily]
INFO: No CSS, PHP, HTML or VueJS files are found in the project. CSS analysis is skipped.
INFO: Sensor SonarCSS Rules [cssfamily] (done) | time=1ms
INFO: Sensor JaCoCo XML Report Importer [jacoco]
INFO: 'sonar.coverage.jacoco.xmlReportPaths' is not defined. Using default locations: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml,build/reports/jacoco/test/jacocoTestReport.xml
INFO: No report imported, no coverage information will be imported by JaCoCo XML Report Importer
INFO: Sensor JaCoCo XML Report Importer [jacoco] (done) | time=2ms
INFO: Sensor JavaXmlSensor [java]
INFO: Sensor JavaXmlSensor [java] (done) | time=0ms
INFO: Sensor HTML [web]
INFO: Sensor HTML [web] (done) | time=8ms
INFO: ------------- Run sensors on project
INFO: Sensor Zero Coverage Sensor
INFO: Sensor Zero Coverage Sensor (done) | time=9ms
INFO: SCM Publisher No SCM system was detected. You can use the 'sonar.scm.provider' property to explicitly specify it.
INFO: CPD Executor Calculating CPD for 0 files
INFO: CPD Executor CPD calculation finished (done) | time=0ms
INFO: Analysis report generated in 202ms, dir size=79 KB
INFO: Analysis report compressed in 39ms, zip size=10 KB
INFO: Analysis report uploaded in 623ms
INFO: ANALYSIS SUCCESSFUL, you can browse http://192.168.33.1:32314/dashboard?id=demo-maven-service
INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
INFO: More about the report processing at http://192.168.33.1:32314/api/ce/task?id=AXOztIcYdI8p8GdhXo8N
INFO: Analysis total time: 13.094 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 25.064s
INFO: Final Memory: 6M/37M
INFO: ------------------------------------------------------------------------

3.3.3 Run SonarScanner on Linux server

cd /usr/local/
sudo wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.4.0.2170-linux.zip
unzip sonar-scanner-cli-4.4.0.2170-linux.zip
mv sonar-scanner-cli-4.4.0.2170-linux sonar-scanner

cd /usr/local/sonar-scanner
$ tree bin/
bin/
├── sonar-scanner
└── sonar-scanner-debug

0 directories, 2 files

$ tree conf/
conf/
└── sonar-scanner.properties

0 directories, 1 file

sudo vim /usr/local/sonar-scanner/conf/sonar-scanner.properties

...
sonar.host.url=http://192.168.33.1:32314

sudo vim /etc/profile.d/scanner.sh

export SONAR_HOME=/usr/local/sonar-scanner/ export PATH=$PATH:$SONAR_HOME/bin

cd demo-maven-service
vim sonarqube.sh

## can run the command in script
sonar-scanner -Dsonar.host.url=http://192.168.33.1:32314 \
-Dsonar.projectKey=demomavenservice \
-Dsonar.projectName=demomavenservice \
--define sonar.login=admin \
--define sonar.password=admin \
--define sonar.projectVersion=1.0 \
--define sonar.ws.timeout=30 \
--define sonar.projectDescription="my first project" \
--define sonar.links.homepage=http://www.baidu.com  \
--define sonar.sources=src \
--define sonar.sourceEncoding=UTF-8 \
--define sonar.java.binaries=target/classes \
--define sonar.java.test.binaries=target/test—classes \
--define sonar.java.surefire.report=target/surefire—reports

Run the script

source sonarqube.sh
INFO: Scanner configuration file: /usr/local/sonar-scanner/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE
INFO: SonarScanner 4.4.0.2170
INFO: Java 11.0.3 AdoptOpenJDK (64-bit)
INFO: Linux 3.10.0-957.12.2.el7.x86_64 amd64
INFO: User cache: /home/vagrant/.sonar/cache
INFO: Scanner configuration file: /usr/local/sonar-scanner/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE
INFO: Analyzing on SonarQube server 8.4.1
INFO: Default locale: "en_US", source code encoding: "UTF-8"
INFO: Load global settings
INFO: Load global settings (done) | time=377ms
INFO: Server id: EA8D9556-AXOeledPvj9r7r4MIG91
INFO: User cache: /home/vagrant/.sonar/cache
...
INFO: Analysis report compressed in 71ms, zip size=14 KB
INFO: Analysis report uploaded in 378ms
INFO: ANALYSIS SUCCESSFUL, you can browse http://192.168.33.1:32314/dashboard?id=demomavenservice
INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
INFO: More about the report processing at http://192.168.33.1:32314/api/ce/task?id=AXO4OmSidI8p8GdhXo8b
INFO: Analysis total time: 14.196 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 16.430s
INFO: Final Memory: 16M/61M
INFO: ------------------------------------------------------------------------

Alt Image Text

3.4 使用流水线进行自动化代码扫描

3.4.1 填写sonarqube scannersharedlibrary

JenkinslibTest/src/org/devops/sonarqube.groovy

package org.devops

//scan
def SonarScan(projectName,projectDesc,projectPath){
    def scannerHome = "/usr/local/sonar-scanner/"
    //定义服务器列表
    def sonarServers = "http://192.168.33.1:32314"
    def sonarDate = sh  returnStdout: true, script: 'date  +%Y%m%d%H%M%S'
    sonarDate = sonarDate - "\n"

    sh """ 
         ${scannerHome}/bin/sonar-scanner -Dsonar.host.url=${sonarServers} \
        -Dsonar.projectKey=${projectName} \
        -Dsonar.projectName=${projectName} \
        --define sonar.login=admin \
        --define sonar.password=admin \
        --define sonar.projectVersion=${sonarDate} \
        --define sonar.ws.timeout=30 \
        --define sonar.projectDescription=${projectDesc} \
        --define sonar.links.homepage=http://www.baidu.com  \
        --define sonar.sources=${projectPath} \
        --define sonar.sourceEncoding=UTF-8 \
        --define sonar.java.binaries=target/classes \
        --define sonar.java.test.binaries=target/test—classes \
        --define sonar.java.surefire.report=target/surefire—reports
    """
}

3.4.2 Pipeline调用sharedlibrary

#!groovy
@Library('jenkinslib@master') _

def build = new org.devops.buildtools()
def sonar = new org.devops.sonarqube()

pipeline {
    agent { node { label "hostmachine" }}
    parameters {
        string(name: 'srcUrl', defaultValue: 'http://192.168.33.1:30088/root/demo-maven-service.git', description: '') 
        choice(name: 'branchName', choices: 'master\nstage\ndev', description: 'Please chose your branch')
        choice(name: 'buildType', choices: 'mvn', description: 'build tool')
        choice(name: 'buildShell', choices: 'clean package -DskipTest\n--version', description: 'build tool')
    }
    stages{
        stage('Checkout') {
            steps {
                script {
                    checkout([$class: 'GitSCM', branches: [[name: "${branchName}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "${srcUrl}"]]])
                } 
            }
        }

        stage('Build') {
            steps {
                script {
                    build.Build(buildType,buildShell)
                } 
            }
        }

        stage('Qa') {
            steps {
                script {
                    sonar.SonarScan("${JOB_NAME}","${JOB_NAME}","src" )
                } 
            }
        }    
    }
 }
stage('Qa') {
    steps {
        script {
            sonar.SonarScan("${JOB_NAME}","${JOB_NAME}","src" )
        } 
    }
}

3.4.3 Console Output

...
[Pipeline] sh
+ date +%Y%m%d%H%M%S
[Pipeline] sh
+ /usr/local/sonar-scanner//bin/sonar-scanner -Dsonar.host.url=http://192.168.33.1:32314 -Dsonar.projectKey=sq4scanner -Dsonar.projectName=sq4scanner --define sonar.login=admin --define sonar.password=admin --define sonar.projectVersion=20200801135329 --define sonar.ws.timeout=30 --define sonar.projectDescription=sq4scanner --define sonar.links.homepage=http://www.baidu.com --define sonar.sources=src --define sonar.sourceEncoding=UTF-8 --define sonar.java.binaries=target/classes --define sonar.java.test.binaries=target/testclasses --define sonar.java.surefire.report=target/surefirereports
INFO: Scanner configuration file: /usr/local/sonar-scanner/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE
INFO: SonarScanner 4.4.0.2170
...
INFO: ANALYSIS SUCCESSFUL, you can browse http://192.168.33.1:32314/dashboard?id=sq4scanner
INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
INFO: More about the report processing at http://192.168.33.1:32314/api/ce/task?id=AXO4zE_WdI8p8GdhXo8n
INFO: Analysis total time: 17.732 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 20.804s
INFO: Final Memory: 16M/62M

Alt Image Text

Alt Image Text

Alt Image Text

Alt Image Text

3.5 使用Sonar插件完成代码扫描

SonarScanner for Jenkins

Jenkins与SonarQube集成

JenkinslibTest/src/org/devops/sonarqubeadv.groovy

package org.devops

//scan
def SonarScan(projectName,projectDesc,projectPath){

    withSonarQubeEnv("sonarqube"){
        def scannerHome = "/usr/local/sonar-scanner/"
        def sonarServers = "http://192.168.33.1:32314"
        def sonarDate = sh  returnStdout: true, script: 'date  +%Y%m%d%H%M%S'
        sonarDate = sonarDate - "\n"

        sh """ 
            ${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=${projectName} \
            -Dsonar.projectName=${projectName} \
            --define sonar.projectVersion=${sonarDate} \
            --define sonar.ws.timeout=30 \
            --define sonar.projectDescription=${projectDesc} \
            --define sonar.links.homepage=http://www.baidu.com \
            --define sonar.sources=${projectPath} \
            --define sonar.sourceEncoding=UTF-8 \
            --define sonar.java.binaries=target/classes \
            --define sonar.java.test.binaries=target/test-classes \
            --define sonar.java.surefire.report=target/surefire-reports
        """
    }
}
 withSonarQubeEnv("sonarqube"){

 }
  • withSonarQubeEnv("sonarqube") 配置在Jenkins中

Alt Image Text

pipeline.groovy

#!groovy
@Library('jenkinslib@master') _

def build = new org.devops.buildtools()
def sonar = new org.devops.sonarqube()
def sonaradv = new org.devops.sonarqubeadv()

pipeline {
    agent { node { label "hostmachine" }}
    parameters {
        string(name: 'srcUrl', defaultValue: 'http://192.168.33.1:30088/root/demo-maven-service.git', description: '') 
        choice(name: 'branchName', choices: 'master\nstage\ndev', description: 'Please chose your branch')
        choice(name: 'buildType', choices: 'mvn', description: 'build tool')
        choice(name: 'buildShell', choices: 'clean package -DskipTest\n--version', description: 'build tool')
    }
    stages{
        stage('Checkout') {
            steps {
                script {
                    checkout([$class: 'GitSCM', branches: [[name: "${branchName}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "${srcUrl}"]]])
                } 
            }
        }

        stage('Build') {
            steps {
                script {
                    build.Build(buildType,buildShell)
                } 
            }
        }

        stage('Qa') {
            steps {
                script {
                    sonaradv.SonarScan("${JOB_NAME}","${JOB_NAME}","src" )
                } 
            }
        }    
    }
 }
  • def sonaradv = new org.devops.sonarqubeadv()
stage('Qa') {
            steps {
                script {
                    sonaradv.SonarScan("${JOB_NAME}","${JOB_NAME}","src" )
                } 
            }
        }

Alt Image Text

  • 点击sonarqube tab

Alt Image Text

3.6 SonarQube项目管理

3.6.1 SonarQube - 创建新的Quality Profiles

  • Name: demo
  • Language: java

Alt Image Text

3.6.1 Activate more rules for demo Profiles

Alt Image Text

Alt Image Text

Alt Image Text

3.6.1 Add demo Profiles to the projects

Alt Image Text

Alt Image Text

3.6.2 SonarQube - 创建新的Quality Gates

Alt Image Text

3.6.2 Add condition to the Quality Gates for projects

Alt Image Text

Add more condition

Alt Image Text

Alt Image Text

3.6.2 Run failed becuase of condition

Alt Image Text

3.6.3 在jenkins pipeline获取SonarQube检测结果 (SonarQube plugin function)

JenkinslibTest/src/org/devops/sonarqubeadv.groovy

package org.devops

//scan
def SonarScan(projectName,projectDesc,projectPath){

    withSonarQubeEnv("sonarqube"){
        def scannerHome = "/usr/local/sonar-scanner/"
        def sonarServers = "http://192.168.33.1:32314"
        def sonarDate = sh  returnStdout: true, script: 'date  +%Y%m%d%H%M%S'
        sonarDate = sonarDate - "\n"

        sh """ 
            ${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=${projectName} \
            -Dsonar.projectName=${projectName} \
            --define sonar.projectVersion=${sonarDate} \
            --define sonar.ws.timeout=30 \
            --define sonar.projectDescription=${projectDesc} \
            --define sonar.links.homepage=http://www.baidu.com \
            --define sonar.sources=${projectPath} \
            --define sonar.sourceEncoding=UTF-8 \
            --define sonar.java.binaries=target/classes \
            --define sonar.java.test.binaries=target/test-classes \
            --define sonar.java.surefire.report=target/surefire-reports
        """
    }

    def qg = waitForQualityGate()
    if (qg.status != 'OK') {
        error "Pipeline aborted due to quality gate failure: ${qg.status}"
    }
}
def qg = waitForQualityGate()
    if (qg.status != 'OK') {
        error "Pipeline aborted due to quality gate failure: ${qg.status}"
    }
  • waitForQualityGate
  • waitForQualityGate: Wait for SonarQube analysis to be completed and return quality gate status

Pipeline script

#!groovy
@Library('jenkinslib@master') _

def build = new org.devops.buildtools()
def sonar = new org.devops.sonarqube()
def sonaradv = new org.devops.sonarqubeadv()

pipeline {
    agent { node { label "hostmachine" }}
    parameters {
        string(name: 'srcUrl', defaultValue: 'http://192.168.33.1:30088/root/demo-maven-service.git', description: '') 
        choice(name: 'branchName', choices: 'master\nstage\ndev', description: 'Please chose your branch')
        choice(name: 'buildType', choices: 'mvn', description: 'build tool')
        choice(name: 'buildShell', choices: 'clean package -DskipTest\n--version', description: 'build tool')
    }
    stages{
        stage('Checkout') {
            steps {
                script {
                    checkout([$class: 'GitSCM', branches: [[name: "${branchName}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "${srcUrl}"]]])
                } 
            }
        }

        stage('Build') {
            steps {
                script {
                    build.Build(buildType,buildShell)
                } 
            }
        }

        stage('Qa') {
            steps {
                script {
                    sonaradv.SonarScan("${JOB_NAME}","${JOB_NAME}","src" )
                } 
            }
        }    
    }
 }

Console output

...
[Pipeline] // withSonarQubeEnv
[Pipeline] waitForQualityGate
Checking status of SonarQube task 'AXO9Z_O3dI8p8GdhXo9M' on server 'sonarqube'
SonarQube task 'AXO9Z_O3dI8p8GdhXo9M' status is 'PENDING'
SonarQube task 'AXO9Z_O3dI8p8GdhXo9M' status is 'SUCCESS'
SonarQube task 'AXO9Z_O3dI8p8GdhXo9M' completed. Quality gate is 'ERROR'
[Pipeline] error

SonarQubeerror 会导致 pipeline 的失败

Alt Image Text

3.6.4 在jenkins pipeline获取SonarQube检测结果 (SonarQube API)

3.6.4 添加SonarQube credentialsJenkins

  • sonar-admin-user

Alt Image Text

  • 查看 SonarQube API的返回
 http://192.168.33.1:32314/api/qualitygates/project_status?projectKey=sq5scanner

{
   "projectStatus":{
      "status":"ERROR",
      "conditions":[
         {
            "status":"OK",
            "metricKey":"coverage",
            "comparator":"LT",
            "errorThreshold":"0",
            "actualValue":"0.0"
         },
         {
            "status":"ERROR",
            "metricKey":"code_smells",
            "comparator":"GT",
            "errorThreshold":"1",
            "actualValue":"3"
         }
      ],
      "periods":[
         {
            "index":1,
            "mode":"PREVIOUS_VERSION",
            "date":"2020-08-01T19:51:35+0000",
            "parameter":"20200801195129"
         }
      ],
      "ignoredConditions":false
   }
}

3.6.4 JenkinslibTest/src/org/devops/sonarapi.groovy

package org.devops

//封装HTTP

def HttpReq(reqType,reqUrl,reqBody){
    def sonarServer = "http://192.168.33.1:32314/api"

    result = httpRequest authentication: 'sonar-admin-user',
            httpMode: reqType, 
            contentType: "APPLICATION_JSON",
            consoleLogResponseBody: true,
            ignoreSslErrors: true, 
            requestBody: reqBody,
            url: "${sonarServer}/${reqUrl}"
            //quiet: true

    return result
}

//获取Sonar质量阈状态
def GetProjectStatus(projectName){
    apiUrl = "project_branches/list?project=${projectName}"
    response = HttpReq("GET",apiUrl,'')

    response = readJSON text: """${response.content}"""
    result = response["branches"][0]["status"]["qualityGateStatus"]

    //println(response)

   return result
}

3.6.4 Pipeline调用sonar api

#!groovy
@Library('jenkinslib@master') _

def build = new org.devops.buildtools()
def sonar = new org.devops.sonarqube()
def sonaradv = new org.devops.sonarqubeadv()
def sonarapi = new org.devops.sonarapi()

pipeline {
    agent { node { label "hostmachine" }}
    parameters {
        string(name: 'srcUrl', defaultValue: 'http://192.168.33.1:30088/root/demo-maven-service.git', description: '') 
        choice(name: 'branchName', choices: 'master\nstage\ndev', description: 'Please chose your branch')
        choice(name: 'buildType', choices: 'mvn', description: 'build tool')
        choice(name: 'buildShell', choices: 'clean package -DskipTest\n--version', description: 'build tool')
    }
    stages{
        stage('Checkout') {
            steps {
                script {
                    checkout([$class: 'GitSCM', branches: [[name: "${branchName}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "${srcUrl}"]]])
                } 
            }
        }

        stage('Build') {
            steps {
                script {
                    build.Build(buildType,buildShell)
                } 
            }
        }

        stage('Qa') {
            steps {
                script {
                    sonaradv.SonarScan("${JOB_NAME}","${JOB_NAME}","src" )

                    result = sonarapi.GetProjectStatus("${JOB_NAME}")
                        println(result)
                    if(result.toString().contains("ERROR")){
                        error "Qaulit gate failed, please re-check ur code!"
                    }else{
                        println(result)
                    }
                } 
            }
        }    
    }
 }

``` script { sonaradv.SonarScan("${JOB_NAME}","${JOB_NAME}","src" )

    result = sonarapi.GetProjectStatus("${JOB_NAME}")
        println(result)
    if(result.toString().contains("ERROR")){
        error "Qaulit gate failed, please re-check ur code!"
    }else{
        println(result)
    }

} ```

Console Output

...
[Pipeline] httpRequest
HttpMethod: GET
URL: http://192.168.33.1:32314/api/project_branches/list?project=sq6scanner
Content-Type: application/json
Using authentication: sonar-admin-user
Sending request to url: http://192.168.33.1:32314/api/project_branches/list?project=sq6scanner
Response Code: HTTP/1.1 200 
Response: 
{"branches":[{"name":"master","isMain":true,"type":"BRANCH","status":{"qualityGateStatus":"OK"},"analysisDate":"2020-08-01T21:28:00+0000","excludedFromPurge":true}]}
Success code from [100‥399]
[Pipeline] readJSON
[Pipeline] echo
OK
[Pipeline] echo
OK
...

3.7 SonarQube搜索与新建项目(API WAY)

http://192.168.33.1:32314/web_api/api

3.7.1 查找项目

api/projects/search?projects=${projectName}" 
//搜索Sonar项目
def SerarchProject(projectName){
    apiUrl = "projects/search?projects=${projectName}"
    response = HttpReq("GET",apiUrl,'')

    response = readJSON text: """${response.content}"""
    result = response["paging"]["total"]

    if(result.toString() == "0"){
       return "false"
    } else {
       return "true"
    }
}

3.7.2 创建新项目

api/projects/create?name=${projectName} &project=$ {projectName} " 
//创建Sonar项目
def CreateProject(projectName){
    apiUrl =  "projects/create?name=${projectName}&project=${projectName}"
    response = HttpReq("POST",apiUrl,'')
    println(response)
}

3.7.3 搜索与新建项目在Pipeline

stage('Qa') {
            steps {
                script {

                    result = sonarapi.SearchProject("${JOB_NAME}")
                    println(result)

                    if(result == "false"){
                       println("${JOB_NAME} ---- The project doesn't exist ----- ${JOB_NAME}!") 
                       sonarapi.CreateProject("${JOB_NAME}")
                    } else {
                       println("${JOB_NAME} ---- The project already exist!")
                    }

                    sonaradv.SonarScan("${JOB_NAME}","${JOB_NAME}","src" )

                    result = sonarapi.GetProjectStatus("${JOB_NAME}")
                    println(result)

                    if(result.toString().contains("ERROR")){
                        error "Quality gate failed, please re-check ur code!"
                    }else{
                        println(result)
                    }
                } 
            }
        } 
 result = sonarapi.SearchProject("${JOB_NAME}")
                    println(result)

                    if(result == "false"){
                       println("${JOB_NAME} ---- The project doesn't exist ----- ${JOB_NAME}!") 
                       sonarapi.CreateProject("${JOB_NAME}")
                    } else {
                       println("${JOB_NAME} ---- The project already exist!")
                    }

Console output

...
[Pipeline] { (Qa)
[Pipeline] script
[Pipeline] {
[Pipeline] httpRequest
HttpMethod: GET
URL: http://192.168.33.1:32314/api/projects/search?projects=sq7scanner
Content-Type: application/json
Using authentication: sonar-admin-user
Sending request to url: http://192.168.33.1:32314/api/projects/search?projects=sq7scanner
Response Code: HTTP/1.1 200 
Response: 
{"paging":{"pageIndex":1,"pageSize":100,"total":0},"components":[]}
Success code from [100‥399]
[Pipeline] readJSON
[Pipeline] echo
false
[Pipeline] echo
sq7scanner ---- The project doesn't exist ----- sq7scanner!
[Pipeline] httpRequest
HttpMethod: POST
URL: http://192.168.33.1:32314/api/projects/create?name=sq7scanner&project=sq7scanner
Content-Type: application/json
Using authentication: sonar-admin-user
Sending request to url: http://192.168.33.1:32314/api/projects/create?name=sq7scanner&project=sq7scanner
Response Code: HTTP/1.1 200 
Response: 
{"project":{"key":"sq7scanner","name":"sq7scanner","qualifier":"TRK","visibility":"public"}}
Success code from [100‥399]
[Pipeline] echo
Status: 200
[Pipeline] withSonarQubeEnv
Injecting SonarQube environment variables using the configuration: sonarqube
[Pipeline] {
[Pipeline] sh
+ date +%Y%m%d%H%M%S
...

Alt Image Text

3.8 配置质量规则与质量阈

3.8.1 配置项目质量规则

确保质量阈已经被创建: java -> demo

Alt Image Text

api/qualityprofiles/add_project?language=${language}&qualityProfile=${qualityProfile}&project=${projectName}"
//配置项目质量规则

def ConfigQualityProfiles(projectName,lang,qpname){
    apiUrl = "qualityprofiles/add_project?language=${lang}&project=${projectName}&qualityProfile=${qpname}"
    response = HttpReq("POST",apiUrl,'')
    println(response)
}
stage('Qa') {
            steps {
                script {

                    result = sonarapi.SearchProject("${JOB_NAME}")
                    println(result)

                    if(result == "false"){
                       println("${JOB_NAME} ---- The project doesn't exist ----- ${JOB_NAME}!") 
                       sonarapi.CreateProject("${JOB_NAME}")
                    } else {
                       println("${JOB_NAME} ---- The project already exist!")
                    }

                    qpName = "${JOB_NAME}".split("-")[0]
                    sonarapi.ConfigQualityProfiles("${JOB_NAME}","java",qpName)

                    sonaradv.SonarScan("${JOB_NAME}","${JOB_NAME}","src" )

                    result = sonarapi.GetProjectStatus("${JOB_NAME}")
                    println(result)

                    if(result.toString().contains("ERROR")){
                        error "Quality gate failed, please re-check ur code!"
                    }else{
                        println(result)
                    }
                } 
            }
        }

Alt Image Text

qpName = "${JOB_NAME}".split("-")[0]
sonarapi.ConfigQualityProfiles("${JOB_NAME}","java",qpName)
  • job name: demo-sq8-scanner

console output

[Pipeline] httpRequest
HttpMethod: POST
URL: http://192.168.33.1:32314/api/qualityprofiles/add_project?language=java&project=demo-sq8-scanner&qualityProfile=demo
Content-Type: application/json
Using authentication: sonar-admin-user
Sending request to url: http://192.168.33.1:32314/api/qualityprofiles/add_project?language=java&project=demo-sq8-scanner&qualityProfile=demo
Response Code: HTTP/1.1 204 
Response: 
null
Success code from [100‥399]
[Pipeline] echo
Status: 204

Alt Image Text

3.8.2 配置项目质量阈

//项目授权
api/permissions/apply_template?projectKey=${projecttKey}&templateName=${templateName}" 

//获取质量阈ID
api/qualitygates/show?name=${gateName}

//更新质量阈值
api/qualitygates/select?projectKey=${projectKey}&gateId=${gateId}" 

JenkinslibTest/src/org/devops/sonarqube.groovy

//获取质量阈ID
def GetQualtyGateId(gateName){
    apiUrl= "qualitygates/show?name=${gateName}"
    response = HttpReq("GET",apiUrl,'')
    response = readJSON text: """${response.content}"""
    result = response["id"]

    return result
}

//配置项目质量阈

def ConfigQualityGates(projectName,gateName){
    gateId = GetQualtyGateId(gateName)
    apiUrl = "qualitygates/select?gateId=${gateId}&projectKey=${projectName}"
    response = HttpReq("POST",apiUrl,'')
    println(response)println(response)
}
sonarapi.ConfigQualityGates("${JOB_NAME}",qpName)

Alt Image Text

3.8.3 使用default QualityGates或者default Qualityfiles

sonarapi.ConfigQualityProfiles("${JOB_NAME}","java","Sonar%20way")

3.8.4 最终版本(sharedlib and pipeline)

JenkinslibTest/src/org/devops/sonarapi.groovy

package org.devops

//封装HTTP

def HttpReq(reqType,reqUrl,reqBody){
    def sonarServer = "http://192.168.33.1:32314/api"

    result = httpRequest authentication: 'sonar-admin-user',
            httpMode: reqType, 
            contentType: "APPLICATION_JSON",
            consoleLogResponseBody: true,
            ignoreSslErrors: true, 
            requestBody: reqBody,
            url: "${sonarServer}/${reqUrl}"
            //quiet: true

    return result
}

//获取Sonar质量阈状态
def GetProjectStatus(projectName){
    apiUrl = "project_branches/list?project=${projectName}"
    response = HttpReq("GET",apiUrl,'')

    response = readJSON text: """${response.content}"""
    result = response["branches"][0]["status"]["qualityGateStatus"]

    //println(response)

   return result
}

//获取Sonar质量阈状态(多分支)
def GetProjectStatus(projectName,branchName){
    apiUrl = "qualitygates/project_status?projectKey=${projectName}&branch=${branchName}"
    response = HttpReq("GET",apiUrl,'')

    response = readJSON text: """${response.content}"""
    result = response["projectStatus"]["status"]

    //println(response)

   return result
}

//搜索Sonar项目
def SearchProject(projectName){
    apiUrl = "projects/search?projects=${projectName}"
    response = HttpReq("GET",apiUrl,'')

    response = readJSON text: """${response.content}"""
    result = response["paging"]["total"]

    if(result.toString() == "0"){
       return "false"
    } else {
       return "true"
    }
}

//创建Sonar项目
def CreateProject(projectName){
    apiUrl =  "projects/create?name=${projectName}&project=${projectName}"
    response = HttpReq("POST",apiUrl,'')
    println(response)
}

//配置项目质量规则

def ConfigQualityProfiles(projectName,lang,qpname){
    apiUrl = "qualityprofiles/add_project?language=${lang}&project=${projectName}&qualityProfile=${qpname}"
    response = HttpReq("POST",apiUrl,'')
    println(response)
}

//获取质量阈ID
def GetQualtyGateId(gateName){
    apiUrl= "qualitygates/show?name=${gateName}"
    response = HttpReq("GET",apiUrl,'')
    response = readJSON text: """${response.content}"""
    result = response["id"]

    return result
}

//配置项目质量阈

def ConfigQualityGates(projectName,gateName){
    gateId = GetQualtyGateId(gateName)
    apiUrl = "qualitygates/select?gateId=${gateId}&projectKey=${projectName}"
    response = HttpReq("POST",apiUrl,'')
    println(response)println(response)
}

pipeline

#!groovy
@Library('jenkinslib@master') _

def build = new org.devops.buildtools()
def sonar = new org.devops.sonarqube()
def sonaradv = new org.devops.sonarqubeadv()
def sonarapi = new org.devops.sonarapi()

pipeline {
    agent { node { label "hostmachine" }}
    parameters {
        string(name: 'srcUrl', defaultValue: 'http://192.168.33.1:30088/root/demo-maven-service.git', description: '') 
        choice(name: 'branchName', choices: 'master\nstage\ndev', description: 'Please chose your branch')
        choice(name: 'buildType', choices: 'mvn', description: 'build tool')
        choice(name: 'buildShell', choices: 'clean package -DskipTest\n--version', description: 'build tool')
    }
    stages{
        stage('Checkout') {
            steps {
                script {
                    checkout([$class: 'GitSCM', branches: [[name: "${branchName}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "${srcUrl}"]]])
                } 
            }
        }

        stage('Build') {
            steps {
                script {
                    build.Build(buildType,buildShell)
                } 
            }
        }

        stage('Qa') {
            steps {
                script {

                    result = sonarapi.SearchProject("${JOB_NAME}")
                    println(result)

                    if(result == "false"){
                       println("${JOB_NAME} ---- The project doesn't exist ----- ${JOB_NAME}!") 
                       sonarapi.CreateProject("${JOB_NAME}")
                    } else {
                       println("${JOB_NAME} ---- The project already exist!")
                    }

                    qpName = "${JOB_NAME}".split("-")[0]
                    sonarapi.ConfigQualityProfiles("${JOB_NAME}","java",qpName)

                    sonarapi.ConfigQualityGates("${JOB_NAME}",qpName)

                    sonaradv.SonarScan("${JOB_NAME}","${JOB_NAME}","src" )

                    result = sonarapi.GetProjectStatus("${JOB_NAME}")
                    println(result)

                    if(result.toString().contains("ERROR")){
                        error "Quality gate failed, please re-check ur code!"
                    }else{
                        println(result)
                    }
                } 
            }
        }    
    }
 }

3.9 Sonar配置项目多分支模式

https://github.com/mc1arke/sonarqube-community-branch-plugin

  • 将插件放到两个目录中,然后重启sonar

    • /opt/sonarqube/extensions/plugins
    • /opt/sonarqube/lib/common
    • 扫描添加 --define sonar.branch.name=${branchName}-X

Alt Image Text