Keep workspace when switching stages in combination with agent none - jenkins-pipeline

I have a Jenkins pipeline where I want to first build my project (Stage A) and trigger an asynchronous long running external test process with the built artifacts. The external test process then resumes the Job using a callback. Afterwards (Stage B) performs some validations of the test results and attaches them to the job. I don't want to block an executor while the external test process is running so I came up with the following Jenkinsfile which mostly suites my needs:
#!/usr/bin/env groovy
pipeline {
agent none
stages {
stage('Stage A') {
agent { docker { image 'my-maven:0.0.17' } }
steps {
script {
sh "rm testfile.txt"
sh "echo ABCD > testfile.txt"
sh "cat testfile.txt"
}
}
}
stage('ContinueJob') {
agent none
input { message "The job will continue once the asynchronous operation has finished" }
steps { echo "Job has been continued" }
}
stage('Stage B') {
agent { docker { image 'my-maven:0.0.17' } }
steps {
script {
sh "cat testfile.txt"
def data = readFile(file: 'testfile.txt')
if (!data.contains("ABCD")) {
error("ABCD not found in testfile.txt")
}
}
}
}
}
}
However, depending on the load of the Jenkins or the time passed or some unknown other conditions, sometimes the files that I create in "Stage A" are no longer available in "Stage B". It seems that Jenkins switches to a different Docker node which causes the loss of workspace data, e.g. in the logs I can see:
[Pipeline] { (Stage A)
[Pipeline] node
Running on Docker3 in /var/opt/jenkins/workspace/TestJob
.....
[Pipeline] stage
[Pipeline] { (Stage B)
[Pipeline] node
Running on Docker2 in /var/opt/jenkins/workspace/TestJob
Whereas with a successful run, it keeps using e.g. node "Docker2" for both stages.
Note that I have also tried reuseNode true within the two docker sections but that didn't help either.
How can I tell Jenkins to keep my workspace files available?

As pointed out by the comment from #Patrice M. if the files are not that big (which is the case for me) stash/unstash are very useful to solve this problem. I have used this combination now since a couple of months and it has solved my issue.

Related

Checking in jenkins whether an artefact exists for another job and branch

I would like to copy an artefact depending on whether it exists in another job. I currently have the following code within a step:
script {
copyArtifacts filter: "dist.tar.gz",
projectName: 'Frontend/${BRANCH_NAME}',
selector: lastSuccessful(),
target: "./public"
}
Now, for example, I want something like this (I realise the syntax is wrong/stupid):
script {
if(file_exists('Frontend/${BRANCH_NAME}').lastSuccessful()) {
copyArtifacts filter: "dist.tar.gz",
projectName: 'Frontend/${BRANCH_NAME}',
selector: lastSuccessful(),
target: "./public"
}
}
So if there is a latest build for the frontend/${BRANCH_NAME}, I would like to execute what it says below. I could now try to perform a check via the browser URL, for example, but isn't there a more elegant solution to do this internally?
I am using a multibranch, thats the reason why I want handle it different.
Edit:
I guess I need to describe my problem a little better after reviewing Geralds suggested solution!
Thank you Gerald for your solution! Unfortunately, it doesn't really fit my problem. You check whether a certain file exists in a project in the branch.
What I need: There is a multi-branch pipeline in which an artefact is created in the respective branch pipeline (e.g. dist.tar.gz) and I would like to check whether this dist.tar.gz is available as an artefact and take the latest version of it.
I can certainly jump over the file system here, but my thought was whether this can be done directly via available methods in Jenkins.
With copyArtifacts, I can simply specify the project name and branch, and it finds the rest on its own. But if the file does not exist, it fails, for example. And finally, depending on the branch, the result is slightly different for my source project, so I need this check if a file exists within another project for a branch. As I said, I am referring to the pipeline and its result (=Artifact) and not to a file in the Git branch.
Edit 2:
Okay, a little progress:
I can definitely retrieve what the last successful build in the multipipeline was with the following code:
def jobName = "My folder/multipipeline/master".
def buildName = Jenkins.instance.getItemByFullName(jobName)
println "Job type: ${buildName.getClass()}"
println "Last success: ${buildName.getLastSuccessfulBuild()}"
println "All builds: ${buildName.getBuilds().collect{ it.getNumber()}}"
println "Last build: ${buildName.getLastBuild()}"
Then displays something like:
Job type: class org.jenkinsci.plugins.workflow.job.WorkflowJob
Last success: my folder/multipipeline/master/dev #21
All builds: [21, 20, 19, 18, 17]
Last build: my folder/multipipeline/master/dev #21
Now I should be able to access an artefact within the Jenkins instance or check if it exists at all
environment {
otherProjectWksp = '../other-project-with-Git-repo/'
otherProjectBranch = 'master'
otherProjectRemoteBranch = 'origin/master'
checkFileExists = 'README.md'
//checkFileExists = 'NOT_EXISTING' // for testing
fileExistsStatus = '-1'
}
stages {
stage('Check if file exists in other project\'s remote branch') {
steps {
dir( otherProjectWksp ) {
script {
fileExistsStatus = sh script: """
#!/bin/bash
git cat-file -e ${otherProjectRemoteBranch}:${checkFileExists} && echo ${checkFileExists} exists
""",
returnStatus: true
}
} // dir
echo "fileExistsStatus: ${fileExistsStatus}"
}
} // stage Check file existence
stage('Copy artifacts') {
when { expression { fileExistsStatus == '0' } }
steps {
echo "Copying artifacts..."
// ...
}
} // stage Copy artifacts
}
Console Output
...
[Pipeline] stage
[Pipeline] { (Check if file exists in other project's remote branch)
[Pipeline] dir
Running in /var/lib/jenkins/workspace/other-project-with-Git-repo
[Pipeline] {
[Pipeline] script
[Pipeline] {
[Pipeline] sh
+ git cat-file -e origin/master:README.md
+ echo README.md exists
README.md exists
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // dir
[Pipeline] echo
fileExistsStatus: 0
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Copy artifacts)
[Pipeline] echo
Copying artifacts...
...

Parallel execution 'mvn test' in Jenkins

I try to create jenkinsfile for parallel execution command mvn test with different arguments. On the first stage of jenkinsfile I create *.csv file where are will be future arguments for mvn test command. Also I don't know the quantity of parallel stages (it depends on first stage where I get data from DB). So, summarize it again. Logic:
First stage for getting data from DB over command mvn test (with args). On this test I save data into csv file.
In loop of jenkinsfile I read every string, parse it and get args for arallel execution mvn test (with args based on the parsed data).
Now it looks like this (only necessary fragments of jenkinsfile):
def buildProject = { a, b, c ->
node {
stage(a) {
catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
sh "mvn -Dtest=test2 test -Darg1=${b} -Darg2=${c}"
}
}
}
}
stages {
stage('Preparation of file.csv') {
steps {
sh 'mvn -Dtest=test1 test'
}
}
stage('Parallel stage') {
steps {
script {
file = readFile "file.csv"
lines = file.readLines()
def branches = [:]
for(i = 0; i < lines.size(); i++) {
values = lines[i].split(';')
branches["${values[0]}"] = { buildProject(values[0], values[1], values[2]) }
}
parallel branches
}
}
}
}
So, which problems do I face now with?
I see in log following error:
[ERROR] The goal you specified requires a project to execute but there is no POM in this directory (/Data/jenkins/workspace//#2)
I look at workspaces of Jenkins and see that there were created several empty(!!!) directories (quantity equals to quantity of parallel stages). And therefore mvn command could be executed because of absence of pom.xml and other files.
In branches the same data are saved on every iteration of loop and in 'stage(a)' I see the same title (but every iteration of loop has unique 'values[0]').
Can you help me with this issue?
Thank you in advance!
So, regarding this jenkins issue issues.jenkins.io/browse/JENKINS-50307 and workaround which could be found there, task could be closed!

Parallel items in Jenkins Declarative pipeline

I am working on setting up automated build and deploy jobs in jenkins
Right now I have a single stage with parallel tasks set up like this
stage ('Testing & documenting'){
steps {
parallel (
"PHPLOC" : {
echo "Running phploc"
sh "./src/vendor/phploc/phploc/phploc --exclude=./src/vendor --no-interaction --quiet --log-csv=./build/logs/loc.csv src tests"
},
"SLOC": {
echo "Running sloc"
sh "sloccount --duplicates --wide --details . > ./build/logs/sloccount.sc 2>/dev/null"
},
"CPD" : {
echo "Running copy-paste detection"
sh "./src/vendor/sebastian/phpcpd/phpcpd --fuzzy . --exclude src/vendor --log-pmd ./build/logs/phpcpd.xml || true"
},
"MD" : {
echo "Running mess detection on code"
sh "./src/vendor/phpmd/phpmd/src/bin/phpmd src xml phpmd_ruleset.xml --reportfile ./build/logs/phpmd_code.xml --exclude vendor,build --ignore-violations-on-exit --suffixes php"
},
"PHPUNIT" : {
echo "Running PHPUnit w/o code coverage"
sh "./src/vendor/phpunit/phpunit/phpunit --configuration phpunit-quick.xml"
}
)
}
}
after reading https://jenkins.io/blog/2018/07/02/whats-new-declarative-piepline-13x-sequential-stages/ I noticed that they use a different structure.
stage("Documenting and Testing") {
parallel {
stage("Documenting") {
agent any
stages {
stage("CPD") {
steps {
//CPD
}
}
stage("PMD") {
steps {
//PMD stuff
}
}
}
stage("Testing") {
agent any
stages {
stage("PHPUnit") {
steps {
//PHPUnit
}
}
}
}
I am not sure what the difference between these two approaches is
The first example running parallel inside the steps block was introduced by the earlier versions of the declarative pipeline. This had some shortcomings. For example, to run each parallel branch on a different agent, you need to use a node step, and if you do that, the output of the parallel branch won’t be available for post directives (at a stage or pipeline level). Basically the old parallel step required you to use Scripted Pipeline within a Declarative Pipeline.
The second example is a true declarative syntax introduced to overcome the shortcomings of the former. In addition, this particular example runs two serial stages within the parallel stage ‘Documenting’.
You can read this official blog to know more about the parallel directive https://jenkins.io/blog/2017/09/25/declarative-1/.

Console Output in pipeline:Jenkins

I have created a complex pipeline. In each stage I have called a job. I want to see the console output for each job in a stage in Jenkins. How to get it?
The object returned from a build step can be used to query the log like this:
pipeline {
agent any
stages {
stage('test') {
steps {
echo 'Building anotherJob and getting the log'
script {
def bRun = build 'anotherJob'
echo 'last 100 lines of BuildB'
for(String line : bRun.getRawBuild().getLog(100)){
echo line
}
}
}
}
}
}
The object returned from the build step is a RunWrapper class object. The getRawBuild() call is returning a Run object - there may be other options than reading the log line-by-line from the looks of this class. For this to work you need to either disable the pipeline sandbox or get script approvals for these methods:
method hudson.model.Run getLog int
method org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper getRawBuild
If you are doing this for many builds, it would be worth putting some code in a pipeline shared library to do what you need or define a function in the pipeline.

How to continue a Jenkins build even though a build step failed?

I am using a Phing build script with Jenkins and would like to run it end to end on a job and capture all the reports. The problem is it stop building on a failed build step. Is there a way or a plugin that would continue the job even on failures?
Thanks
I don't know a lot about Phing but, since it's based on Ant, if the build step you are executing has a "failonerror" attribute you should be able to set it to false so that the entire build doesn't fail if the step returns an error.
Yes, use try, catch block in you pipeline scripts
example:
try {
// do some stuff that potentially fails
} catch (error) {
// do stuff if try fails
} finally {
// when you need some clean up to do
}
Or alternatively if you use sh commands to run these tests, consider running your sh scripts with the "|| true" suffix, this tells the linux sh script to exit with a result code of 0, even if your real command exited with an exit code.
example:
stage('Test') {
def testScript = ""
def testProjects = findFiles(glob: 'test/**/project.json')
if (!fileExists('reports/xml')) {
if (!fileExists('reports')) {
sh "mkdir reports"
}
sh "mkdir reports/xml"
}
for(prj in testProjects) {
println "Test project located, running tests: " + prj.path
def matcher = prj.path =~ 'test\\/(.+)\\/project.json'
testScript += "dotnet test --no-build '${prj.path}' -xml 'reports/xml/${matcher[0][1]}.Results.xml' || true\n"
}
sh testScript

Resources