天天看點

Jenkins Pipeline內建Sonar進行代碼品質檢測Jenkins Pipeline內建Sonar進行代碼品質檢測

Jenkins Pipeline內建Sonar進行代碼品質檢測

簡介

jenkins pipeline

Jenkins Pipeline (或簡稱為 "Pipeline" )是一套jenkins插件,将持續傳遞的實作和實施內建到 Jenkins 中。

Jenkins Pipeline 表達了這樣一種流程:将基于版本控制管理的軟體持續的傳遞到您的使用者和消費者手中。

Jenkins Pipeline 提供了一套可擴充的工具,用于将“簡單到複雜”的傳遞流程實作為“持續傳遞即代碼”。 Jenkins Pipeline 的定義通常被寫入到一個文本檔案(稱為

Jenkinsfile

)中,該檔案可以被檢入到項目的源代碼控制庫中。

摘自

Jenkins官方文檔

SonarQube

SonarQube is an open source platform to perform automatic reviews with static analysis of code to detect bugs, code smells and security vulnerabilities on 25+ programming languages including Java, C#, JavaScript, TypeScript, C/C++, COBOL and more.

SonarQube是一個開源的平台,以執行與代碼的靜态分析,自動審查,可以檢測在25+的程式設計語言如Java,C#,JavaScript,TypeScript,C/C++,COBOL等的代碼缺陷和安全漏洞。

OWASP

OWASP,全稱是:Open Web Application Security Project,翻譯為中文就是:開放式Web應用程式安全項目,是一個非營利組織,不附屬于任何企業或财團,這也是該組織可以不受商業控制地進行安全開發及安全普及的重要原因,

詳細介紹

。OWASP Dependency-Check,它識别項目依賴關系,并檢查是否存在任何已知的、公開的、漏洞,基于OWASP Top 10 2013。

場景

在devops理念中,CI/CD毫無疑問是最重要的一環,而代碼品質檢查則是CI中必不可少的一步。在靈活開發的思想下,代碼的疊代周期變短,傳遞速度提升,這個時候代碼的品質就很難保證,測試隻能保證功能完整與可用,而代碼的品質純靠review的話效率又很低,這個時候sonar就可以很好的幫助開發自動化檢測代碼品質,降低bug數量,也可以根據掃描結果養成良好的程式設計習慣,同時也可以減少測試的工作量,真正提升整個團隊效率,實作devops理念。

前提

jenkins、sonarqube服務已經搭建完成

版本:jenkins2.166,sonarqube6.7.6

配置

  1. 下載下傳安裝jenkins插件
    **[系統管理]**-**[插件管理]**-**[可選插件]**-**[SonarQube Scanner for Jenkins]**
    
    ![image](http://wx4.sinaimg.cn/large/ad5fbf65ly1g0u4q3ae1bj20t90233yt.jpg)
               
  2. SonarQube生成token,這個token不會顯示第二次,是以一定要記住
    ![image](https://ws1.sinaimg.cn/mw690/ad5fbf65ly1g0u5902q6nj213f0hgwgn.jpg)
               
  3. SonarQube配置Dependency-Check
    **[配置]**-**[Dependency-Check]**
    
    **[注意:]**這裡去掉 ```${WORKSPACE}/```,否則将報```[INFO] Dependency-Check XML report does not exists. Please check property sonar.dependencyCheck.reportPath:/data/jenkinsHome/workspace/xxx/${WORKSPACE}/dependency-check-report.xml```
    
    ![image](https://ws1.sinaimg.cn/large/ad5fbf65ly1g0yvjjcvdaj211b0jhgod.jpg)
               
  4. 配置jenkins
    **[系統管理]**-**[系統設定]**-**[SonarQube servers]**
    
    ![image](http://wx3.sinaimg.cn/large/ad5fbf65ly1g0u50l8q4lj215o0b3myw.jpg)
               
  5. sonar添加webhook
    在代碼掃描成功後,掃描結果需要回調jenkins,添加的Jenkins的webhook結構為:http://[jenkins_url]/sonarqube-webhook/
    
    **[配置]**-**[web回調接口]**-**[URL]**
    
    ![image](https://ws3.sinaimg.cn/large/ad5fbf65ly1g0v4m590vhj212k0pw0vo.jpg)
               
  6. 在pom.xml檔案中添加
<plugin>
            <groupId>org.sonarsource.scanner.maven</groupId>
            <artifactId>sonar-maven-plugin</artifactId>
            <version>3.6.0.1398</version>
        </plugin>           
  1. 編輯jenkins pipeline

    在jenkinsfile檔案中添加配置

    stage('依賴安全檢查') {
        steps{
            dependencyCheckAnalyzer datadir: '', hintsFile: '', includeCsvReports: false, includeHtmlReports: true, includeJsonReports: false, includeVulnReports: true, isAutoupdateDisabled: false, outdir: '', scanpath: '', skipOnScmChange: false, skipOnUpstreamChange: false, suppressionFile: '', zipExtensions: ''
        }
    }
    
    stage('靜态代碼檢查') {
        steps {
            echo "starting codeAnalyze with SonarQube......"
            withSonarQubeEnv('sonar') {
                //注意這裡withSonarQubeEnv()中的參數要與之前SonarQube servers中Name的配置相同
                withMaven(maven: 'M3') {
                    sh "mvn clean install -Dmaven.test.skip=true sonar:sonar -Dsonar.projectKey={項目key} -Dsonar.projectName={項目名稱} -Dsonar.projectVersion={項目版本} -Dsonar.sourceEncoding=UTF-8 -Dsonar.exclusions=src/test/** -Dsonar.sources=src/ -Dsonar.java.binaries=target/classes -Dsonar.host.url={SonarQube位址} -Dsonar.login={SonarQube的token}"
                }
            }
            script {
                timeout(1) {
                    //這裡設定逾時時間1分鐘,不會出現一直卡在檢查狀态
                    //利用sonar webhook功能通知pipeline代碼檢測結果,未通過品質阈,pipeline将會fail
                    def qg = waitForQualityGate('sonar')
                    //注意:這裡waitForQualityGate()中的參數也要與之前SonarQube servers中Name的配置相同
                    if (qg.status != 'OK') {
                        error "未通過Sonarqube的代碼品質阈檢查,請及時修改!failure: ${qg.status}"
                    }
                }
            }
        }
    }
               
    參數解釋:
    • sonar.projectKey:項目key (必填項)
    • sonar.projectName:項目名稱(必填項)
    • sonar.projectVersion:項目版本(必填項)
    • sonar.sources:源碼位置(相對路徑)
    • sonar.java.binaries:編譯後的class位置(必填項,相對路徑同上)
    • sonar.exclusions:排除的掃描的檔案路徑
    • sonar.host.url:SonarQube位址
    • sonar.login:SonarQube生成的token

運作

執行jenkins建構,建構成功後會顯示如下,則證明sonar代碼掃描成功且通過代碼品質阈檢查

檢視sonar報告,這裡有兩種方式

  • 可直接登入SonarQube檢視報告
  • 也可直接在jenkins頁面點選SonarQube圖示進入,點選以下标記均可進去

其他

問題一:無法掃描代碼,錯誤提示

hudson.remoting.ProxyException: hudson.AbortException: SonarQube installation defined in this job (sonar) does not match any configured installation. Number of installations that can be configured: 1.
If you want to reassign jobs to a different SonarQube installation, check the documentation under https://redirect.sonarsource.com/plugins/jenkins.html
    at hudson.plugins.sonar.SonarInstallation.checkValid(SonarInstallation.java:94)
    at hudson.plugins.sonar.SonarBuildWrapper.setUp(SonarBuildWrapper.java:67)
    at org.jenkinsci.plugins.workflow.steps.CoreWrapperStep$Execution.start(CoreWrapperStep.java:80)
    at org.jenkinsci.plugins.workflow.cps.DSL.invokeStep(DSL.java:268)
Caused: hudson.remoting.ProxyException: org.codehaus.groovy.runtime.InvokerInvocationException: hudson.AbortException: SonarQube installation defined in this job (sonar) does not match any configured installation. Number of installations that can be configured: 1.
If you want to reassign jobs to a different SonarQube installation, check the documentation under https://redirect.sonarsource.com/plugins/jenkins.html
    at org.jenkinsci.plugins.workflow.cps.CpsStepContext.replay(CpsStepContext.java:499)
    at org.jenkinsci.plugins.workflow.cps.DSL.invokeStep(DSL.java:295)
    at org.jenkinsci.plugins.workflow.cps.DSL.invokeStep(DSL.java:207)
    at org.jenkinsci.plugins.workflow.cps.DSL.invokeDescribable(DSL.java:395)
    at org.jenkinsci.plugins.workflow.cps.DSL.invokeMethod(DSL.java:179)
    at org.jenkinsci.plugins.workflow.cps.CpsScript.invokeMethod(CpsScript.java:122)
    at sun.reflect.GeneratedMethodAccessor1200.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:42)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:157)
    at org.kohsuke.groovy.sandbox.GroovyInterceptor.onMethodCall(GroovyInterceptor.java:23)
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:155)
    at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:155)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:159)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:129)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:129)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:129)
    at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:17)
Caused: hudson.remoting.ProxyException: java.lang.IllegalArgumentException: Failed to prepare withSonarQubeEnv step
    at org.jenkinsci.plugins.workflow.cps.DSL.invokeDescribable(DSL.java:397)
    at org.jenkinsci.plugins.workflow.cps.DSL.invokeMethod(DSL.java:179)
    at org.jenkinsci.plugins.workflow.cps.CpsScript.invokeMethod(CpsScript.java:122)
    at sun.reflect.GeneratedMethodAccessor1200.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:42)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:157)
    at org.kohsuke.groovy.sandbox.GroovyInterceptor.onMethodCall(GroovyInterceptor.java:23)
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:155)
    at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:155)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:159)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:129)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:129)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:129)
    at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:17)
    at WorkflowScript.run(WorkflowScript:27)
    at ___cps.transform___(Native Method)
    at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:57)
    at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:109)
    at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixArg(FunctionCallBlock.java:82)
    at sun.reflect.GeneratedMethodAccessor249.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
    at com.cloudbees.groovy.cps.impl.ClosureBlock.eval(ClosureBlock.java:46)
    at com.cloudbees.groovy.cps.Next.step(Next.java:83)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:174)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:163)
    at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:122)
    at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:261)
    at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:163)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$101(SandboxContinuable.java:34)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.lambda$run0$0(SandboxContinuable.java:59)
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox.runInSandbox(GroovySandbox.java:121)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:58)
    at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:182)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:332)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$200(CpsThreadGroup.java:83)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:244)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:232)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:64)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:131)
    at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
    at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:59)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Finished: FAILURE
           

原因:withSonarQubeEnv()中的參數與之前SonarQube servers中Name的配置不同,導緻沒有找到找到SonarQube

問題二:SonarQube的token配置不對,導緻無法連接配接sonar

[ERROR] Failed to execute goal org.sonarsource.scanner.maven:sonar-maven-plugin:3.6.0.1398:sonar (default-cli) on project callcenter: Not authorized. Please check the properties sonar.login and sonar.password. -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
[Pipeline] }
[withMaven] artifactsPublisher - Archive artifact pom.xml under cn/keking/callcenter/callcenter/0.0.1-SNAPSHOT/callcenter-0.0.1-SNAPSHOT.pom
[withMaven] artifactsPublisher - Archive artifact target/callcenter-0.0.1-SNAPSHOT.jar under cn/keking/callcenter/callcenter/0.0.1-SNAPSHOT/callcenter-0.0.1-SNAPSHOT.jar
[withMaven] artifactsPublisher - Archive artifact target/callcenter-0.0.1-SNAPSHOT-api.jar under cn/keking/callcenter/callcenter/0.0.1-SNAPSHOT/callcenter-0.0.1-SNAPSHOT-api.jar
[withMaven] junitPublisher - Archive test results for Maven artifact cn.keking.callcenter:callcenter:jar:0.0.1-SNAPSHOT generated by maven-surefire-plugin:test (default-test): target/surefire-reports/*.xml
[withMaven] junitPublisher - Jenkins JUnit Attachments Plugin not found, can't publish test attachments.Recording test results
None of the test reports contained any result
[withMaven] Jenkins Task Scanner Plugin not found, don't display results of source code scanning for 'TODO' and 'FIXME' in pipeline screen.
[withMaven] Publishers: Pipeline Graph Publisher: 1 ms, Generated Artifacts Publisher: 891 ms, Junit Publisher: 4 ms, Dependencies Fingerprint Publisher: 5 ms
[Pipeline] // withMaven
[Pipeline] }
WARN: Unable to locate 'report-task.txt' in the workspace. Did the SonarScanner succedeed?
           

原因:sonar.login的token配置不正确或者沒有配置

問題三:jenkins pipeline在SonarQube回調時顯示逾時

[Pipeline] waitForQualityGate
Checking status of SonarQube task 'AWlX97LSgWqXn-z33SO5' on server 'sonar'
SonarQube task 'AWlX97LSgWqXn-z33SO5' status is 'IN_PROGRESS'
Cancelling nested steps due to timeout
           

原因:SonarQube沒有配置webhook回調,導緻請求逾時,按照步驟4配置webhook即可解決

問題四:sonar找不到Dependency-Check XML

[INFO] Sensor Dependency-Check [dependencycheck]
[INFO] Process Dependency-Check report
[INFO] Dependency-Check XML report does not exists. Please check property sonar.dependencyCheck.reportPath:/data/jenkinsHome/workspace/xxx/${WORKSPACE}/dependency-check-report.xml
[INFO] Analysis skipped/aborted due to missing report file
[INFO] Dependency-Check HTML report does not exists. Please check property sonar.dependencyCheck.htmlReportPath:/data/jenkinsHome/workspace/xxx/${WORKSPACE}/dependency-check-report.html
[INFO] HTML-Dependency-Check report does not exist.
           

原因:SonarQube配置Dependency-Check插件有誤,按照上文配置即可

結語

sonar與jenkins內建的方式還有很多,不止pipeline+maven這一種,還有配置在jenkins建構任務中、直接使用sonar腳本等方法。采用這樣方法,一方面是配置相對簡單,不需要每個建構任務都進行配置,隻需要将jenkinsfile中拷入相應代碼并修改幾個參數即可。同時可以在靜态代碼掃描期間完整maven打包,減少持續內建的時間。

作者簡介:

郭旭東,2018年4月加入凱京科技。曾任進階研發和運維開發工程師,現任凱京科技研發中心架構&運維部運維負責人,負責公司運維團隊建設。緻力于推行devops理念及相關技術,提升開發效率,提高傳遞品質與速度,專注于雲平台的容器化實踐,探索更高效的運維系統架構。