×
Find plugins

Violation Comments to GitLab
2.24Minimum Jenkins requirement: 2.1ID: violation-comments-to-gitlab

This plugin uses Violation Comments to GitLab Lib. It will find report files from static code analysis and comment GitLab merge requests with the content.


Build Status


This is a Jenkins plugin for Violation Comments to GitLab Lib. This plugin will find report files from static code analysis and comment GitLab pull requests with the content.


Available in Jenkins here.


Example of supported reports are available here.


There is a complete running example available here: https://github.com/tomasbjerre/jenkins-configuration-as-code-sandbox


A number of parsers have been implemented. Some parsers can parse output from several reporters.


Reporter Parser Notes
ARM-GCC CLANG
AndroidLint ANDROIDLINT
AnsibleLint FLAKE8 With -p
CLang CLANG
CPD CPD
CPPCheck CPPCHECK
CPPLint CPPLINT
CSSLint CSSLINT
Checkstyle CHECKSTYLE
CodeNarc CODENARC
Detekt CHECKSTYLE With --output-format xml.
DocFX DOCFX
Doxygen CLANG
ERB CLANG With erb -P -x -T '-' "${it}" | ruby -c 2>&1 >/dev/null | grep '^-' | sed -E 's/^-([a-zA-Z0-9:]+)/${filename}\1 ERROR:/p' > erbfiles.out.
ESLint CHECKSTYLE With format: 'checkstyle'.
Findbugs FINDBUGS
Flake8 FLAKE8
FxCop FXCOP
GCC CLANG
Gendarme GENDARME
GoLint GOLINT
GoVet GOLINT Same format as GoLint.
GolangCI-Lint CHECKSTYLE With --out-format=checkstyle.
GoogleErrorProne GOOGLEERRORPRONE
IAR IAR With --no_wrap_diagnostics
Infer PMD Facebook Infer. With --pmd-xml.
JCReport JCREPORT
JSHint JSHINT
KTLint CHECKSTYLE
Klocwork KLOCWORK
KotlinGradle KOTLINGRADLE Output from Kotlin Gradle Plugin.
KotlinMaven KOTLINMAVEN Output from Kotlin Maven Plugin.
Lint LINT A common XML format, used by different linters.
MSCpp MSCPP
Mccabe FLAKE8
MyPy MYPY
NullAway GOOGLEERRORPRONE Same format as Google Error Prone.
PCLint PCLINT PC-Lint using the same output format as the Jenkins warnings plugin, details here
PHPCS CHECKSTYLE With phpcs api.php --report=checkstyle.
PHPPMD PMD With phpmd api.php xml ruleset.xml.
PMD PMD
Pep8 FLAKE8
PerlCritic PERLCRITIC
PiTest PITEST
Puppet-Lint CLANG With -log-format %{fullpath}:%{line}:%{column}: %{kind}: %{message}
PyDocStyle PYDOCSTYLE
PyFlakes FLAKE8
PyLint PYLINT With pylint --output-format=parseable.
ReSharper RESHARPER
RubyCop CLANG With rubycop -f clang file.rb
SbtScalac SBTSCALAC
Scalastyle CHECKSTYLE
Simian SIMIAN
Sonar SONAR With mvn sonar:sonar -Dsonar.analysis.mode=preview -Dsonar.report.export.path=sonar-report.json.
Spotbugs FINDBUGS
StyleCop STYLECOP
SwiftLint CHECKSTYLE With --reporter checkstyle.
TSLint CHECKSTYLE With -t checkstyle
XMLLint XMLLINT
YAMLLint YAMLLINT With -f parsable
ZPTLint ZPTLINT


Missing a format? Open an issue here!


There is also:



Notify Jenkins from GitLab



Merge


You must perform the merge before build. If you don't perform the merge, the reported violations will refer to other lines then those in the pull request. The merge can be done with a shell script like this.


echo ---
echo --- Merging from $FROM in $FROMREPO to $TO in $TOREPO
echo ---
git clone $TOREPO
cd *
git reset --hard $TO
git status
git remote add from $FROMREPO
git fetch from
git merge $FROM
git --no-pager log --max-count=10 --graph --abbrev-commit

Your build command here!


Screenshots


Comments can be made on the diff with one comment per violation createSingleFileComments.


Example comment on diff


Or one big comment can be made, createCommentWithAllSingleFileComments.


Example comment


Job DSL Plugin


This plugin can be used with the Job DSL Plugin. Here is an example using Generic Webhook Trigger pluginHTTP Request Plugin and Conditional BuildStep Plugin.


job('GitLab_MR_Builder') {
 concurrentBuild()
 quietPeriod(0)
 parameters {
  stringParam('MERGE_REQUEST_TO_URL', '')
  stringParam('MERGE_REQUEST_FROM_URL', '')
  stringParam('MERGE_REQUEST_TO_BRANCH', '')
  stringParam('MERGE_REQUEST_FROM_BRANCH', '')
 }
 scm {
  git {
   remote {
    name('origin')
    url('$MERGE_REQUEST_TO_URL')
   }
   remote {
    name('upstream')
    url('$MERGE_REQUEST_FROM_URL')
   }
   branch('$MERGE_REQUEST_FROM_BRANCH')
   extensions {
    mergeOptions {
     remote('upstream')
     branch('$MERGE_REQUEST_TO_BRANCH')
    }
   }
  }
 }
 triggers {
  genericTrigger {
   genericVariables {
    genericVariable {
     key("MERGE_REQUEST_TO_URL")
     value("\$.object_attributes.target.git_http_url")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MERGE_REQUEST_FROM_URL")
     value("\$.object_attributes.source.git_http_url")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MERGE_REQUEST_TO_BRANCH")
     value("\$.object_attributes.target_branch")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MERGE_REQUEST_FROM_BRANCH")
     value("\$.object_attributes.source_branch")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("PROJECT_ID")
     value("\$.object_attributes.target_project_id")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MERGE_REQUST_ID")
     value("\$.object_attributes.id")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MR_OBJECT_KIND")
     value("\$.object_kind")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MR_OLD_REV")
     value("\$.object_attributes.oldrev")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MR_ACTION")
     value("\$.object_attributes.action")
     expressionType("JSONPath")
     regexpFilter("")
    }
   }
   regexpFilterText("\$MR_OBJECT_KIND \$MR_ACTION \$MR_OLD_REV")
   regexpFilterExpression("^merge_request\\s(update\\s.{40}\$|open.*)")
  }
 }
 steps {
  httpRequest {
   url("http://gitlab:880/api/v3/projects/\$PROJECT_ID/merge_requests/\$MERGE_REQUST_ID/notes?private_token=AvAkp6HtUvzpesPypXSk")
   consoleLogResponseBody(true)
   httpMode("POST")
   requestBody('body=Building... %20\$BUILD_URL')
   }
 
  shell('./gradlew build')
 
  conditionalBuilder {
   runCondition {
    statusCondition {
     worstResult('SUCCESS')
     bestResult('SUCCESS')
    }
    runner {
     runUnstable()
    }
    conditionalbuilders {
     httpRequest {
      url('http://gitlab:880/api/v3/projects/\$PROJECT_ID/merge_requests/\$MERGE_REQUST_ID/notes?private_token=AvAkp6HtUvzpesPypXSk')
      consoleLogResponseBody(true)
      httpMode('POST')
      requestBody('body=SUCCESS%20\$BUILD_URL')
     }
    }
   }
  }
 
  conditionalBuilder {
   runCondition {
    statusCondition {
     worstResult('FAILURE')
     bestResult('FAILURE')
    }
    runner {
     runUnstable()
    }
    conditionalbuilders {
     httpRequest {
      url('http://gitlab:880/api/v3/projects/\$PROJECT_ID/merge_requests/\$MERGE_REQUST_ID/notes?private_token=AvAkp6HtUvzpesPypXSk')
      consoleLogResponseBody(true)
      httpMode('POST')
      requestBody('body=FAIL%20\$BUILD_URL')
     }
    }
   }
  }
 }
 publishers {
  violationsToGitLabRecorder {
   config {
    gitLabUrl("http://gitlab:880/")
    projectId("\$PROJECT_ID")
    mergeRequestIid("\$MERGE_REQUST_IID")
    
    // Only specify proxy if you need it
    proxyUri('')
    proxyUser('')
    proxyPassword('')
 
    commentOnlyChangedContent(true)
    createSingleFileComments(true)
    createCommentWithAllSingleFileComments(true)
    minSeverity('INFO')
    maxNumberOfViolations(99999)
    
    //You may want this when troubleshooting things
    enableLogging(true)
    
 
    apiToken("")
    apiTokenCredentialsId("gitlabtoken")
    apiTokenPrivate(true)
    authMethodHeader(true)
    ignoreCertificateErrors(true)
    
    commentTemplate("""
 **Reporter**: {{violation.reporter}}{{#violation.rule}}
 
 **Rule**: {{violation.rule}}{{/violation.rule}}
 **Severity**: {{violation.severity}}
 **File**: {{violation.file}} L{{violation.startLine}}{{#violation.source}}
 
 **Source**: {{violation.source}}{{/violation.source}}
 
 {{violation.message}}
 """)
 
    violationConfigs {
     violationConfig {
      parser("FINDBUGS")
      reporter("Findbugs")
      pattern(".*/findbugs/.*\\.xml\$")
     }
     violationConfig {
      parser("CHECKSTYLE")
      reporter("Checkstyle")
      pattern(".*/checkstyle/.*\\.xml\$")
     }
    }
   }
  }
 }
}


Pipeline Plugin


Here is an example pipeline that will merge, run unit tests, run static code analysis and finally report back to GitLab. It requires the GitLab Plugin.


pipelineJob("merge-request-pipeline") {
 concurrentBuild()
 quietPeriod(0)
 authenticationToken("thetoken")
 triggers {
  genericTrigger {
   genericVariables {
    genericVariable {
     key("MERGE_REQUEST_TO_URL")
     value("\$.object_attributes.target.git_http_url")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MERGE_REQUEST_FROM_URL")
     value("\$.object_attributes.source.git_http_url")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MERGE_REQUEST_TO_BRANCH")
     value("\$.object_attributes.target_branch")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MERGE_REQUEST_FROM_BRANCH")
     value("\$.object_attributes.source_branch")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("PROJECT_ID")
     value("\$.object_attributes.target_project_id")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("PROJECT_PATH")
     value("\$.object_attributes.target.path_with_namespace")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MERGE_REQUST_IID")
     value("\$.object_attributes.iid")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MR_OBJECT_KIND")
     value("\$.object_kind")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MR_OLD_REV")
     value("\$.object_attributes.oldrev")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MR_ACTION")
     value("\$.object_attributes.action")
     expressionType("JSONPath")
     regexpFilter("")
    }
    genericVariable {
     key("MR_TITLE")
     value("\$.object_attributes.title")
     expressionType("JSONPath")
     regexpFilter("")
    }
   }
   regexpFilterText("\$MR_OBJECT_KIND \$MR_ACTION \$MR_OLD_REV")
   regexpFilterExpression("^merge_request\\s(update\\s.{40}\$|open.*)")
  }
 }
 
 definition {
  cps {
   script(readFileFromWorkspace('merge_request_pipeline.pipeline'))
   sandbox()
  }
 }
}


And the merge_request_pipeline.pipeline contains


def commentMr(projectId, mergeRequestIid, comment) {
 def body = comment
 .replaceAll(" ","%20")
 .replaceAll("/","%2F")
 def project = projectId
 .replaceAll("/","%2F")
 sh "curl http://gitlab:80/api/v4/projects/$project/merge_requests/$mergeRequestIid/notes -H 'PRIVATE-TOKEN: 6xRcmSzPzzEXeS2qqr7R' -X POST -d \"body="+body+"\""
}
  
node {
 deleteDir()
 currentBuild.description = "$MR_TITLE from $MERGE_REQUEST_FROM_BRANCH to $MERGE_REQUEST_TO_BRANCH"
  
 commentMr(env.PROJECT_PATH,env.MERGE_REQUST_IID,"Verifierar $MERGE_REQUEST_FROM_BRANCH... ${env.BUILD_URL}")
  
 stage('Merge') {
  sh "git init"
  sh "git fetch --no-tags $MERGE_REQUEST_TO_URL +refs/heads/*:refs/remotes/origin/* --depth=200"
  sh "git checkout origin/${env.MERGE_REQUEST_TO_BRANCH}"
  sh "git config user.email 'je@nkins.domain'"
  sh "git config user.name 'jenkins'"
  sh "git merge origin/${env.MERGE_REQUEST_FROM_BRANCH}"
  sh "git log --graph --abbrev-commit --max-count=10"
 }
  
 stage('Compile') {
  sh "./gradlew assemble"
 }
  
 stage('Unit test') {
  sh "./gradlew test"
  commentMr(env.PROJECT_PATH,env.MERGE_REQUST_IID,"Test ok in $MERGE_REQUEST_FROM_BRANCH =) ${env.BUILD_URL}")
 }
  
 stage('Regression test') {
  sh "echo regtest"
  commentMr(env.PROJECT_PATH,env.MERGE_REQUST_IID,"Regression test ok in $MERGE_REQUEST_FROM_BRANCH =) ${env.BUILD_URL}")
 }
  
 stage('Static code analysis') {
 sh "./gradlew check"
 step([
 $class: 'ViolationsToGitLabRecorder',
 config: [
  gitLabUrl: 'http://gitlab:80/',
  projectId: env.PROJECT_PATH,
  mergeRequestIid: env.MERGE_REQUST_IID,
  commentOnlyChangedContent: true,
  createSingleFileComments: true,
  createCommentWithAllSingleFileComments: true,
  minSeverity: 'INFO',
  maxNumberOfViolations: 99999,
 
  // You may want this when troubleshooting things
  enableLogging: true,
 
  // Only specify proxy if you need it
  proxyUri: '',
  proxyUser: '',
  proxyPassword: '',
 
  // Specify one of these
  apiToken: '6xRcmSzPzzEXeS2qqr7R',
  apiTokenCredentialsId: 'id',
 
  apiTokenPrivate: true,
  authMethodHeader: true,
  ignoreCertificateErrors: true,
  keepOldComments: false,
  shouldSetWip: true,
 
  commentTemplate: """
 **Reporter**: {{violation.reporter}}{{#violation.rule}}
 
 **Rule**: {{violation.rule}}{{/violation.rule}}
 **Severity**: {{violation.severity}}
 **File**: {{violation.file}} L{{violation.startLine}}{{#violation.source}}
 
 **Source**: {{violation.source}}{{/violation.source}}
 
 {{violation.message}}
 """,
 
  violationConfigs: [
   [ pattern: '.*/checkstyle/.*\\.xml$', parser: 'CHECKSTYLE', reporter: 'Checkstyle' ],
   [ pattern: '.*/findbugs/.*\\.xml$', parser: 'FINDBUGS', reporter: 'Findbugs' ],
   [ pattern: '.*/pmd/.*\\.xml$', parser: 'PMD', reporter: 'PMD' ],
  ]
 ]
 ])
 }
}

Changelog

https://github.com/jenkinsci/violation-comments-to-gitlab-plugin/blob/master/CHANGELOG.md

ArchivesGet past versions
Labels