Import cfn-lint report to Jenkins through Warnings-NG plugin - jenkins

I'm using Warnings-NG plugin to import all reports generated by Pylint and YamlLint succesfully. Now I want to import the cfn-lint (cloudformation yaml files) reports but I'm having problems.
I've generated the cfn-lint reports using -f parseable --output-file report.out and imported it to Jenkins with Warnings-NG's recordIssues tool: yamlLint(pattern: 'report.out') without errors, but unfortunately no detected errors in the cloudformation template were imported.
After that, I tried change the format report to Junit and import it using the available Junit tool.
// Generate the report:
cfn-lint . -f junit --output-file report-junit.out
// Import the junit report:
recordIssues tool: junitParser(pattern: 'report-junit.out')
In this last case I'm having Java errors.
Has anyone tried to import the cfn-lint reports into jenkins or another type of application?
Any idea is welcome.
Regards.

I raised this issue on Violations Lib to add to support to Cfn-Lint report Junit format, now it was fixed and will be available in the next release of Warnings-NG Jenkins Plugin.
If you don't want wait for it, I created a workaround, it basically takes the : (colon) delimited file (cfn-lint . -f parseable --output-file report.out) and modify it to format that Warnings-NG RecordIssues YamlLint expects.
I used this Groovy code to do that:
def cfnLogFile = readFile "report.out"
def cfnLogFileRows = cfnLogFile.readLines()
cfnLogLine = ""
for (row in cfnLogFileRows){
def (c1, c2, c3, c4, c5, c6, c7) = row.tokenize(":")
if (c6 ==~ /E.*/){
level = "[error]"
} else if (c6 ==~ /W.*/){
level = "[warning]"
} else {
level = "[information]"
}
cfnLogLine = c1 + ":" + c2 + ":" + c3 + ": " + level + " " + c6 + " " + c7
sh "echo '${cfnLogLine}' >> report_parsed.out"
}
Now, in other step you can upload the report_parsed.out:
recordIssues(
enabledForFailure: false,
tool: yamlLint(pattern: '**/report_parsed.out', reportEncoding: 'UTF-8', id: 'cfn-lint', name: 'Cfn-Lint')
)

Related

Jenkins lts : Problem with groovy script path in jenkins workspace

I just upgraded the jenkins version of our development factory to lts. We were previously in an old 2.x version
In fact we have a stage to load all scripts :
stage('Load Utilities Scripts') {
checkout scm
echo "${job_name}"
modules.messages = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/slack_functions.groovy"
modules.ansible = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/ansible_functions.groovy"
modules.jenkins = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/jenkins_functions.groovy"
modules.pic_env = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/pic_env.groovy"
modules.maven = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/maven_functions.groovy"
modules.git = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/git_functions.groovy"
}
it worked perfectly well except that now the scripts are positioned in
/var/jenkins_home/workspace/SAGES2_BUILD_AUTO#script/357f3acc22b0fbb05a66735d46d7d7eb950a2e836ab3762b1905784bc550ee5e on builtin
/var/jenkins_home/workspace/SAGES2_BUILD_AUTO on builtin
I guess it's a security evolution (i know groovy script plugin on jenkins are often deprecated but it's another problem).
How can i get this string : 357f3acc22b0fbb05a66735d46d7d7eb950a2e836ab3762b1905784bc550ee5e ? Is there any vars like job_name or jenkins_home ? Who generates this token and where ? Can i have the hand on this string ?
What does it represent ?
You can get the current directory and build the absolute path by appending the relative part.
modules.messages = load pwd() + "#script/pipelines/utils/slack_functions.groovy"
Or something like this
modules.messages = load "${WORKSPACE}#script/pipelines/utils/slack_functions.groovy"
It seems this has being introduced as a security improvement. So you will have to work around this like below.
script {
// Locate the jenkins folder
// This is done because there is a new sub-folder (like : 17a4ba1ed1ce777b18c5...)
git_jenkins_folder = sh (
script: "find " + WORKSPACE + "#script -type d -name 'jenkins' -printf '%T# %Tc %p\n' | sort -rn | head -1 | cut -d' ' -f9",
returnStdout: true
).trim()
// Load the groovy methods in groovy files
slack = load git_jenkins_folder + "/pipelines/utils/slack_functions.groovy"
}
Credit: Why does Jenkins creates a subfolder within the workspace#script folder to checkout git code instead of the workspace#script itself?

How to specify correct report-task.txt file in SonarQube Jenkins plugin?

I am running a SonarQube analysis of my Node.js project in Jenkins. Sonar analysis creates report-task.txt file with the result. Unfortunately some modules in the node_modules folder also contain their report-task.txt files which results in a warning in Jenkins:
WARN: Found multiple 'report-task.txt' in the workspace. Taking the first one.
/jenkins/workspace/.scannerwork/report-task.txt
/jenkins/workspace/node_modules/some_module/.scannerwork/report-task.txt
/jenkins/workspace/node_modules/some_other_module/.scannerwork/report-task.txt
Everything is fine until the Sonar analysis for my project fails: then Jenkins takes a different report-task.txt file from a module in node_modules as a result of a Sonar analysis:
WARN: Found multiple 'report-task.txt' in the workspace. Taking the first one.
/jenkins/workspace/node_modules/some_module/.scannerwork/report-task.txt <- wrong file
/jenkins/workspace/node_modules/some_other_module/.scannerwork/report-task.txt
Is there a way to specify that /jenkins/workspace/.scannerwork/report-task.txt is the only correct result file and Jenkins should ignore all the other? Preferably using Jenkins Pipelines
This is a snippet from my pipeline:
sh 'cat .scannerwork/report-task.txt'
// copy to a properties file so we can ingest as variables
sh 'cp .scannerwork/report-task.txt .scannerwork/report-task.properties'
def props = readProperties file: '.scannerwork/report-task.properties'
def ceTaskUrl= props['ceTaskUrl']
def sonarServerUrl=props['serverUrl']
// wait for analysis to complete
waitUntil {
def response = httpRequest consoleLogResponseBody: true, contentType: 'APPLICATION_JSON', ignoreSslErrors: true, url: "${ceTaskUrl}", wrapAsMultipart: false
println "Sent a request, got a $response response"
def body = readJSON text: response.content
body.each { key, value -> }
ceTask = body.task.status
echo "Status is "+ceTask
if ("FAILED".equals(ceTask)){
echo "failed = "+ceTask
throw new Exception(failed+" Sonar process failed!")
}
return "SUCCESS".equals(ceTask)
sleep 30
}

How do I get the build number from where I executed a rebuild?

When I click "rebuild" from the page of a build jenkins rebuilds and runs a new job- a new job with a new jenkins build number.
How do I get the build number of the job where I executed the rebuild?
Im not talking about the previous build number.
Say Im on build 10. I go to build 5 and click rebuild. How do I that build number (5) from inside the pipeline like I can with env.BUILD_NUMBER?
I assume that you are using Groovy Pipeline and already know the Global Variable (see Global Variable Reference).
The currentBuild variable has a field rawBuild that return a hudson.model.Run object
Call rawBuildObject#getCauses() or rawBuildObject#getCauses() and return some Cause object.
script below:
node {
stage('test advance script') {
echo "current build number: ${currentBuild.number}"
echo "previous build number: ${currentBuild.previousBuild.getNumber()}"
def causes = currentBuild.rawBuild.getCauses()
echo "causes: ${causes}"
def rebuildCause0 = currentBuild.rawBuild.getCause(com.sonyericsson.rebuild.RebuildCause)
echo "rebuildCause0: ${rebuildCause0}"
echo "rebuild up number: ${rebuildCause0.getUpstreamBuild()}"
}
}
But as we discuss in chat, the Rebuilder Plugin use CauseAction in a wrong way. If it is fixed as this, console output should be:
current build number: 72
previous build number: 71
causes: [hudson.model.Cause$UserIdCause#679c1066, job/DMP/job/test-pipeline/63[hudson.model.Cause$UserIdCause#679c1066]]
rebuildCause0: job/DMP/job/test-pipeline/63[hudson.model.Cause$UserIdCause#679c1066]
rebuild up number: 63
Remember to scriptApproval when you see errors like this:
Scripts not permitted to use method hudson.model.Run
getCauses. Administrators can decide whether to approve or reject this
signature.
Assuming you can curl your own jenkins builds:
def getOriginalRebuildNum(String jobUrl = "${JOB_URL}"){
def urlJsonPath
def buildNumber
def result
ansiColor('xterm') {
try {
buildNumber = "${BUILD_NUMBER}"
urlJsonPath = "${JOB_URL}/${buildNumber}/api/json"
while (!buildNumber.isEmpty()) {
result = buildNumber
buildNumber = sh(
script:
"""
curl -s $urlJsonPath \
| jq -r '.actions[]
| select(."_class" == "hudson.model.CauseAction") .causes[]
| select(.upstreamBuild != null).upstreamBuild'
""",
returnStdout: true).trim()
urlJsonPath = "${JOB_URL}/${buildNumber}/api/json"
}
echo "Original Build: ${JOB_URL}/${result}/"
}
catch (err) {
"Error: Could not retrieve original Build Number from ${urlJsonPath} - $err"
result = "${BUILD_NUMBER}"
}
}
return result
}

Jenkins Declarative Pipeline, run groovy script on slave agent

I have a Jenkins declarative pipeline I have been running on the Jenkins master and it works fine. However, now that I have moved to trying to execute this on a slave node, the groovy scripts which are called in the pipeline can not access the files in the workspace.
My jenkinsfile looks like this...
pipeline {
agent {
label {
label "windows"
customWorkspace "WS-${env.BRANCH_NAME}"
}
}
stages {
stage('InitialSetup') {
steps {
"${env.WORKSPACE}/JenkinsScripts/myScript.groovy"
}
}
}
I can see on the slave that it is creating the workspace, doing the checkout from git, and executing the script correctly. However, if something in the script try's to interact with the files in the workspace it fails.
If I have something simple like this...
def updateFile(String filename) {
echo env.NODE_NAME
filename = "${env.WORKSPACE}/path/to/file"
def myFile = new File(filename)
<do other things with the file>
}
...it says it can not find the file specified. It gives me the path it is looking for and I can confirm the file exists, and that the code runs when just building on the master.
Why can the script not find the files this way when in can just running on the master node? I added the "echo env.NODE_NAME" command into my groovy file and it says the script is executing on the correct node.
Thanks.
Turns out Groovy File commands are considered insecure, and although they will run on the master, they will not run on the slave. If you call them from a script that has the agent set to another node, it will still execute the command just fine, just on the master node, not the agent. Here's an excerpt of an article post https://support.cloudbees.com/hc/en-us/articles/230922508-Pipeline-Files-manipulation
The operation with File class are run on master, so only works if build is run on master, in this example I create a file and check if I can access it on a node with method exists, it does not exist because the new File(file) is executed on master, to check this I search for folder Users that exist on my master but not in the node.
stage 'file move wrong way'
//it only works on master
node('slave') {
def ws = pwd()
def context = ws + "/testArtifact"
def file = ws + '/file'
sh 'touch ' + file
sh 'ls ' + ws
echo 'File on node : ' + new File(file).exists()
echo 'Users : ' + new File('/Users').exists()
sh 'mv ' + file + ' ' + context
sh 'ls ' + ws
}
To execute file manipulation command we recommend to use native commands.
This is a simple example of operations in shell
stage 'Create file'
sh 'touch test.txt'
stage 'download file'
def out='$(pwd)/download/maven.tgz'
sh 'mkdir -p ./download'
sh 'curl -L http://ftp.cixug.es/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz -o ' + out
stage 'move/rename'
def newName = 'mvn.tgz'
sh 'mkdir -p $(pwd)/other'
sh 'mv ' + out + ' ' + newName
sh 'cp ' + newName + ' ' + out
}
I run into this same issue recently. I had a python file that runs and writes the results to a JSON file. I was trying to access the JSON file to retrieve the data from there. Here is the code I was using inside a stage block of a declarative pipeline:
script {
def jsonSlurper = new JsonSlurper()
def fileParsed = new File("parameters.json")
def dataJSON = jsonSlurper.parse(fileParsed)
}
As everyone stated already, the above was failing with FileNotFoundException because anything inside script{} will only run on master and not the agent.
To work around the issue, I have used the Pipeline Utility Steps plugin (reference: https://plugins.jenkins.io/pipeline-utility-steps/ -- How to use: https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#writejson-write-json-to-a-file-in-the-workspace)
The plugin will allow you to do any read/write operation on multiple file formats.
Here is an example of the code I used after installing the plugin:
script {
def props = readJSON file: 'parameters.json'
println("just read it..")
println(props)
}
Note: I was using jenkins 2.249.1
I have implemented the code which automatically installs Groovy on slave (for scripted pipeline). Perhaps this solution is a little bit cumbersome, but pipelines don't offer any other way to achieve the same functionality as "Execute Groovy Script" stuff from the old Jenkins, because the plugin https://wiki.jenkins.io/display/JENKINS/Groovy+plugin is not supported yet for pipeline.
import hudson.tools.InstallSourceProperty;
import hudson.tools.ToolProperty;
import hudson.tools.ToolPropertyDescriptor;
import hudson.tools.ToolDescriptor;
import hudson.tools.ToolInstallation;
import hudson.tools.ToolInstaller;
import hudson.util.DescribableList;
import hudson.plugins.groovy.GroovyInstaller;
import hudson.plugins.groovy.GroovyInstallation;
/*
Installs Groovy on the node.
The idea was taken from: https://devops.lv/2016/12/05/jenkins-groovy-auto-installer/
and https://github.com/jenkinsci/jenkins-scripts/blob/master/scriptler/configMavenAutoInstaller.groovy
COMMENT 1: If we use this code directly (not as a separate method) then we get
java.io.NotSerializableException: hudson.plugins.groovy.GroovyInstaller
COMMENT 2: For some reason inst.getExecutable(channel) returns null. I use inst.forNode(node, null).getExecutable(channel) instead.
TODO: Check if https://jenkinsci.github.io/job-dsl-plugin/#method/javaposse.jobdsl.dsl.helpers.step.MultiJobStepContext.groovyCommand
works better.
*/
#NonCPS
def installGroovyOnSlave(String version) {
if ((version == null) || (version == "")) {
version = "2.4.7" // some default should be
}
/* Set up properties for our new Groovy installation */
def node = Jenkins.getInstance().slaves.find({it.name == env.NODE_NAME})
def proplist = new DescribableList<ToolProperty<?>, ToolPropertyDescriptor>()
def installers = new ArrayList<GroovyInstaller>()
def autoInstaller = new GroovyInstaller(version)
installers.add(autoInstaller)
def InstallSourceProperty isp = new InstallSourceProperty(installers)
proplist.add(isp)
def inst = new GroovyInstallation("Groovy", "", proplist)
/* Download and install */
autoInstaller.performInstallation(inst, node, null)
/* Define and add our Groovy installation to Jenkins */
def descriptor = Jenkins.getInstance().getDescriptor("hudson.plugins.groovy.Groovy")
descriptor.setInstallations(inst)
descriptor.save()
/* Output the current Groovy installation's path, to verify that it is ready for use */
def groovyInstPath = getGroovyExecutable(version)
println("Groovy " + version + " is installed in the node " + node.getDisplayName())
}
/* Returns the groovy executable path on the current node
If version is specified tries to find the specified version of groovy,
otherwise returns the first groovy installation that was found.
*/
#NonCPS
def getGroovyExecutable(String version=null) {
def node = Jenkins.getInstance().slaves.find({it.name == env.NODE_NAME})
def channel = node.getComputer().getChannel()
for (ToolInstallation tInstallation : Jenkins.getInstance().getDescriptor("hudson.plugins.groovy.Groovy").getInstallations()) {
if (tInstallation instanceof GroovyInstallation) {
if ((version == null) || (version == "")) {
// any version is appropriate for us
return tInstallation.forNode(node, null).getExecutable(channel)
}
// otherwise check for version
for (ToolProperty prop in tInstallation.getProperties()) {
if (prop instanceof InstallSourceProperty) {
for (ToolInstaller tInstaller: prop.installers) {
if (
(tInstaller instanceof GroovyInstaller) &&
(tInstaller.id.equals(version))
)
return tInstallation.forNode(node, null).getExecutable(channel)
}
}
}
}
}
return null
}
/* Wrapper function. Returns the groovy executable path as getGroovyExecutable()
but additionally tries to install if the groovy installation was not found.
*/
def getGroovy(String version=null) {
def installedGroovy = getGroovyExecutable(version)
if (installedGroovy != null) {
return installedGroovy
} else {
installGroovyOnSlave(version)
}
return getGroovyExecutable(version)
}
Just put these 3 methods to your pipeline script and you will be able to get the Groovy executable path with the help of the method getGroovy(). If it is not installed yet then the installation will be done automatically. You can test this code with the simple pipeline, like this:
// Main
parallel(
'Unix' : {
node ('build-unix') {
sh(getGroovy() + ' --version')
}
},
'Windows' : {
node ('build-win') {
bat(getGroovy() + ' --version')
}
}
)
For me the output was:
[build-unix] Groovy Version: 2.4.7 JVM: 1.8.0_222 Vendor: Private Build OS: Linux
[build-win] Groovy Version: 2.4.7 JVM: 11.0.1 Vendor: Oracle Corporation OS: Windows 10
To work with files on the slave workspace use the readFile, writeFile, findFiles etc steps.
Or if they are large as FloatingCoder said use native tooling; which may be running a groovy script.
A workaround could be load the library via sh command in Jenkinsfile.
So, if you use in Jenkinsfile:
sh 'groovy libraryName.groovy'
You can load the lib locally and in this way you can store File on the slave node.
Even without pipelines, there is no option to restrict a job based on slave agent label. So, I think, pipelines are only for master node execution.
Starting from release 2.4 of the Groovy plugin there is withGroovy step available which sets up the environment on the agent so that you can do sh 'groovy yourscript.groovy' with expected environments. It also enables limited interaction between Pipeline and groovy script.
See https://www.jenkins.io/doc/pipeline/steps/groovy/ for some details about the step.

How to append a text to a file in jenkinsfile

How to append text to a file in a Jenkinsfile injecting the Jenkins BUILD_ID
I wish to see:
version := "1.0.25"
where 25 is the BUILD_ID
Here is my attempt:
import hudson.EnvVars
node {
stage('versioning'){
echo 'retrieve build version'
sh 'echo version := 1.0.${env.BUILD_ID} >> build.sbt'
}
}
Error:
version:=1.0.${env.BUILD_ID}: bad substitution
Note the file is in the current directory
The pipeline built in writeFile is also very useful here but requires a read+write process to append to a file.
def readContent = readFile 'build.sbt'
writeFile file: 'build.sbt', text: readContent+"\r\nversion := 1.0.${env.BUILD_ID}"
env.BUILD_ID is a groovy variable, not a shell variable. Since you used single-quotes (') groovy will not substitute the variables in your string and the shell doesn't know about ${env.BUILD_ID}. You need to either use double-quotes " and let groovy do the substitution
sh "echo version := 1.0.${env.BUILD_ID} >> build.sbt"
or use the variable the shell knows
sh 'echo version := 1.0.$BUILD_ID >> build.sbt'
and since you need the version surrounded with doublequotes, you'd need something like this:
sh "echo version := \\\"1.0.${env.BUILD_ID}\\\" >> build.sbt"
I've used dirty little wrapper function to implement Stefan Crain's answer above:
def appendFile(String fileName, String line) {
def current = ""
if (fileExists(fileName)) {
current = readFile fileName
}
writeFile file: fileName, text: current + "\n" + line
}
I really don't like it, but it does the trick and it gets round escaping quotes via slashy strings,e.g.:
def tempFile = '/tmp/temp.txt'
writeFile file: tempFile, text: "worthless line 1\n"
// now append the string 'version="1.2.3" # added by appendFile\n' to tempFile
appendFile(tempFile,/version="1.2.3" # added by appendFile/ + "\n")

Resources