I need to read a simple csv file and going through the documentation I found this readCSV method which comes with Jenkins, I have set a sample file named test.csv in the workspace folder and use this simple test pipeline:
'''
pipeline {
agent any
stages {
stage('read csv') {
steps {
script{
def records = readCSV file: 'test.csv'
println records
}
}
}
}
}
'''
But I keep getting the No such DSL method 'readCSV' error and I am not sure what it means, I have read here in SO that usually means you lack a plugin but this does not seem to be the case
Apparently the pipeline-utility-steps plugin was not installed and I thought it was installed by default, in case anyone else faces the same issue.
Related
I'm newbie to Jenkins pipeline and writing a groovy script to parse a json file. However I'm facing an error which many have faced but none of the solutions worked for me. Below is my Jenkinsfile and error msg.
def envname = readJSON file: '${env.WORKSPACE}/manifest.json'
pipeline {
agent any
stages {
stage('Build') {
steps {
echo WORKSPACE
sh "ls -a ${WORKSPACE}"
}
}
}
}
[Pipeline] Start of Pipeline
[Pipeline] readJSON
[Pipeline] End of Pipeline
org.jenkinsci.plugins.workflow.steps.MissingContextVariableException:
Required context class hudson.FilePath is missing Perhaps you forgot
to surround the code with a step that provides this, such as: node at
org.jenkinsci.plugins.pipeline.utility.steps.AbstractFileOrTextStepExecution.run(AbstractFileOrTextStepExecution.java:30)
I even tried readJSON file: '${WORKSPACE}/manifest.json but that didn't work too. I'm sure the mistake is with the first line since when removing that line, there execution is successful. The docs are pretty helpful but I'm not able to track down where exactly I'm going wrong that is why posted here.
UPDATE:
I tried the following methods def envname = readJSON file: "./manifest.json" and def envname = readJSON file: "${env.WORKSPACE}/manifest.json" and even tried them defining under the steps block. Nothing worked. Below is the error msg I recieved when I defined them under step block
WorkflowScript: 5: Expected a step # line 7, column 13
def envname =
^
Below is the official syntax doc of readJson and I can see that I'm using the correct syntax only. but still doesn't work as expected.
https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#readjson-read-json-from-files-in-the-workspace
'${env.WORKSPACE}/manifest.json' is interpolating the Groovy env map as a shell variable. You need to interpolate it as a Groovy variable like "${env.WORKSPACE}/manifest.json".
sh "ls -a ${WORKSPACE}" is interpolating the shell environment variable WORKSPACE as a Groovy variable. You need to interpolate it as a shell variable like sh 'ls -a ${WORKSPACE}'.
echo WORKSPACE is attempting to resolve the shell variable WORKSPACE as a first class Groovy variable expression. You need to use the Groovy env map instead like echo env.WORKSPACE.
As for the global variable indefinite type assignment on the first line: if it still throws the error above after making those fixes, then it may be due to invalid use of scripted syntax in a declarative syntax pipeline. You likely need to place it inside a step block within your pipeline in that case.
I've solved this myself with the help of "Matt Schuchard"'s below answer. I'm not sure whether this is the only way to solve but this worked for me.
pipeline {
agent any
stages {
stage('Json-Build') {
steps {
script {
def envname = readJSON file: "${env.WORKSPACE}/manifest.json"
element1 = "${envname.dev}"
echo element1
}
}
}
}
}
I have a very simple pipeline which works on a master. I was reading a line in a tmp.txt which works on Jenkins (master).
stage ('Stage 1'){
node('master') {
File file1 = new File("env.Workspace/tmp.txt")
def String my_line = file1.readLines().get(0)
…
}
}
I’ve have to move the stage to other one node (slave) and it doesn’t work anymore. If there is a tmp.txt in a workspace of master – pipeline reads it. But I want to read the tmp.txt in a workspace of node, not from master!
stage ('Stage 1'){
node('Agent_1') {
File file1 = new File("env.Workspace/tmp.txt")
def String my_line = file1.readLines().get(0)
…
}
}
I've found an info that:
“File always implies a file path on the current computer”.
What does it mean? It must be possible to read a file from node..
Can anybody help there?
Do not use native Groovy/Java IO functions, but use pipeline steps instead. The reason for this is that the pipeline code itself is always executed on the master!
The correct (pseudo) code, using the readFile step, would be like:
stage ('Stage 1'){
node('Agent_1') {
def String my_line = readFile("tmp.txt")
…
}
}
I'm putting together a Jenkins pipeline job which will take a file parameter. I can trigger the job and point it at a file however I can't find where the file has ended up (In an ordinary freestyle job it would be in the workspace).
Where has the uploaded file gone? Or do file parameters not currently work with pipelines?
There is currently an issue with pipeline and file parameter
(https://issues.jenkins-ci.org/browse/JENKINS-27413).
Solved it the following way:
node {
deleteDir()
stage("upload") {
def inputFile = input message: 'Upload file', parameters: [file(name: 'data.zip')]
new hudson.FilePath(new File("$workspace/data.zip")).copyFrom(inputFile)
inputFile.delete()
}
stage("checkout") {
echo fileExists('data.zip').toString()
}
}
I know the solution is not that beautiful because the pipeline gets interrupted for the upload but it works.
Further the "copyFrom" is necessary, because the input stores the "data.zip" in the jobs directory and not in the workspace (don't know why)
Found a WA (Strictly for text based file input)
We can use Jenkins multi-line string parameter and ask user to paste file contents to it.
And in our pipeline, write contents of this parameter using pipeline step writeFile, as :
stage('File Param WA') {
writeFile file: 'demo.yaml', text: params.DEMO_YAML
}
I tried using the solution provided by #Christoph Forster , but the input File was not getting copied anywhere in the workspace .
So I used the workaround as provided in
https://bitbucket.org/janvrany/jenkins-27413-workaround-library/src/6b7dada8ea37?at=default
The library provides a new library - unstashParam - that saves the file build parameter into a workspace. Works fine with text and yaml file .
I also tried using the solution by #Christoph Forster but I received a script security error when Groovy Sandbox is enable
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use new hudson.FilePath java.io.File
However, it seems we can skip the file copying and deleting actions (and bypass the Groovy sandbox restriction) by simply requiring that the file is uploaded to the job workspace. Just add the workspace variable to the file name as follows:
stage("upload") {
def inputFile = input message: 'Upload file', parameters: [file(name: "$workspace/data.zip")]
}
I found a solution in the form of a global library here:
https://bitbucket.org/janvrany/jenkins-27413-workaround-library/src/default/
It contains calls to inner methods of Jenkins which are deprecated (I guess).
So I made my own version like this:
import hudson.FilePath
import hudson.model.ParametersAction
import hudson.model.FileParameterValue
import hudson.model.Executor
def call(String name, String fname = null) {
def paramsAction = currentBuild.rawBuild.getAction(ParametersAction.class);
if (paramsAction == null) {
error "unstashParam: No file parameter named '${name}'"
}
for (param in paramsAction.getParameters()) {
if (param.getName().equals(name)) {
if (! param instanceof FileParameterValue) {
error "unstashParam: not a file parameter: ${name}"
}
if (env['NODE_NAME'] == null) {
error "unstashParam: no node in current context"
}
if (env['WORKSPACE'] == null) {
error "unstashParam: no workspace in current context"
}
workspace = new FilePath(getComputer(env['NODE_NAME']), env['WORKSPACE'])
filename = fname == null ? param.getOriginalFileName() : fname
file = workspace.child(filename)
file.copyFrom(param.getFile())
return filename;
}
}
}
def getComputer(name){
for(computer in Jenkins.getInstance().getComputers()){
if(computer.getDisplayName() == name){
return computer.getChannel()
}
}
error "Cannot find computer for file parameter workaround"
}
You can insert it in a global library and then use it like:
library "file-workaround"
node {
def file_in_workspace = unstashParam "myFile"
sh "cat ${file_in_workspace}"
}
It's not pretty but it's working and as long as there is no official fix, it's my best shot.
Update
Turns out you might run into "No such file or directory". That's because nothing in the workaround triggers Jenkins to create the workspace directory. If that was triggered somewhere else in the pipeline good, otherwise you'll be scratching your head.
You might wanna throw a
touch "thisIsAFile"
in there
To handle an optional file parameter in pipeline (to handle the use case where no file should be accepted) you could use jenkinsci-unstashParam-library (add it in Jenkins>Manage Jenkins>Configure System>Global Pipeline Libraries https://github.com/janvrany/jenkinsci-unstashParam-library) with a try/catch in a script as this sample stage:
stage('upload') {
steps {
// delete workspace
cleanWs()
// handle file parameters in pipeline (JENKINS-27413)
script {
try {
// force workspace directory creation
sh "touch emptyFileToCreateWorkspace"
// https://stackoverflow.com/questions/59468464/fetching-uploaded-files-in-jenkins
def file_in_workspace = unstashParam 'MY_FILE.xlsx'
// https://unix.stackexchange.com/questions/125776/error-with-a-file-name-containing-parentheses
sh "mv '${file_in_workspace}' MY_FILE.xlsx"
}
catch (Exception e) {
echo e.getMessage()
echo "No file parameter, we will continue.."
}
}
}
}
File parameters provides 2 alternative parameters types for files (stashed for large files and base64 for small files).
Example, for base64File:
node {
sh 'echo $FILE | base64 -d'
withFileParameter('FILE') {
sh 'cat $FILE'
}
}
and stashedFile:
node {
unstash 'FILE'
sh 'cat FILE'
}
Tried what Christoph suggested and it didnt work for me. Here is what worked for me and the setup which I have, his should help others figure out what to do.
Problem:
I am executing my pipeline on dedicated nodes and use sanitized workspaces. After some research and troubleshooting I found out that by default the file upload only works with Master node. I realized this after digging through the file system and finding the file I am uploading in the workspace on the master
Solution:
stage('Upload Key') {
agent { label 'master' }
steps {
script {
// Uploads file via master node and stases it for other nodes to access
def inputFile = input message: 'Upload file', parameters: [file(name: "key.p12")]
new hudson.FilePath(new File("${workspace}/key.p12")).copyFrom(inputFile)
inputFile.delete()
}
stash name: 'key.p12' , includes: "key.p12"
}
}
stage('Register') {
steps {
ws (sanitizedWorkspaceName) {
echo "Registering"
unstash 'key.p12'
}
}
}
Execute the suggested file copy solution by Christoph. This stores the file in the job workspace on the master node
Allow the scripts in Manage Jenkins > In Process Script approval
use the stash step to stash the uploaded file
In the target stage "running on a different node" use the unstash
Hope this helps
I wasn't able to make Christoph's solution working if the file was uploaded on master node and needed on slave. The solution was to stash it on master and later unstash it on slave. Don't forget to remove the uploaded file on master node.
It's supported by the latest File Parameters plugin now. Please refer to: How to pass a file parameter to another build job in jenkins pipeline?
I have several pipeline jobs, which are configured very similarly.
They all have the same stages (of which there are about 10).
I am now I am thinking about moving to the declarative pipeline (https://jenkins.io/blog/2016/09/19/blueocean-beta-declarative-pipeline-pipeline-editor/).
But I do not want to define the ~10 stages in every pipeline. I want to define them at one place, and "import" them somehow.
Is this possible with declarative pipelines at all? I see that there are Libraries, but it does not seem like I could include the stage definition using them.
You will have to create a shared-library to implement what i am about to suggest. For shared-library implementation, you may check the following posts:
Using Building Blocks in Jenkins Declarative Pipeline
Upload file in Jenkins input step to workspace (Mainly for images so one can easily figure out things)
Now if you want to use a Jenkinsfile (kind of a template) which can be reused across multiple projects (jobs), then that is indeed possible.
Once you have created a shared-library repository with vars directory in it, then you just have to create a Groovy file (let's say, commonPipeline.groovy) inside vars directory.
Here's an example that works because I have used it earlier in multiple jobs.
$ cat shared-lib/vars/commonPipeline.groovy
// You can create function(s) as shown below, if required
def someFunctionA() {
// Your code
}
// This is where you will define all the stages that you want
// to run as a whole in multiple projects (jobs)
def call(Map config) {
pipeline {
agent {
node { label 'slaveA || slaveB' }
}
environment {
myvar_Y = 'apple'
myvar_Z = 'orange'
}
stages {
stage('Checkout') {
steps {
deleteDir()
checkout scm
}
}
stage ('Build') {
steps {
script {
check_something = someFunctionA()
if (check_something) {
echo "Build!"
# your_build_code
} else {
error "Something bad happened! Exiting..."
}
}
}
}
stage ('Test') {
steps {
echo "Running tests..."
// your_test_code
}
}
stage ('Deploy') {
steps {
script {
sh '''
# your_deploy_code
'''
}
}
}
}
post {
failure {
sh '''
# anything_you_need_to_perform_in_failure_step
'''
}
success {
sh '''
# anything_you_need_to_perform_in_success_step
'''
}
}
}
}
With above Groovy file in place, all you have to do now is to call it in your various Jenkins projects. Since you might already be having an existing Jenkinsfile (if not, create it) in your Jenkins project, you just have to replace the existing content of that file with the following:
$ cat Jenkinsfile
// Assuming you have named your shared-library as `my-shared-lib` & `Default version` to `master` branch in
// `Manage Jenkins` » `Configure System` » `Global Pipeline Libraries` section
#Library('my-shared-lib#master')_
def params = [:]
params=[
jenkins_var: "${env.JOB_BASE_NAME}",
]
commonPipeline params
Note: As you can see above, I am calling commonPipeline.groovy file. So, all your bulky Jenkinsfile will get reduced to just five or six lines of code, and those few lines are also going to be common across all those projects. Also note that I have used jenkins_var above. It can be any name. It's not actually used but is required for pipeline to run. Some Groovy expert can clarify that part.
Ref: https://www.jenkins.io/blog/2017/10/02/pipeline-templates-with-shared-libraries/
I'm putting together a Jenkins pipeline job which will take a file parameter. I can trigger the job and point it at a file however I can't find where the file has ended up (In an ordinary freestyle job it would be in the workspace).
Where has the uploaded file gone? Or do file parameters not currently work with pipelines?
There is currently an issue with pipeline and file parameter
(https://issues.jenkins-ci.org/browse/JENKINS-27413).
Solved it the following way:
node {
deleteDir()
stage("upload") {
def inputFile = input message: 'Upload file', parameters: [file(name: 'data.zip')]
new hudson.FilePath(new File("$workspace/data.zip")).copyFrom(inputFile)
inputFile.delete()
}
stage("checkout") {
echo fileExists('data.zip').toString()
}
}
I know the solution is not that beautiful because the pipeline gets interrupted for the upload but it works.
Further the "copyFrom" is necessary, because the input stores the "data.zip" in the jobs directory and not in the workspace (don't know why)
Found a WA (Strictly for text based file input)
We can use Jenkins multi-line string parameter and ask user to paste file contents to it.
And in our pipeline, write contents of this parameter using pipeline step writeFile, as :
stage('File Param WA') {
writeFile file: 'demo.yaml', text: params.DEMO_YAML
}
I tried using the solution provided by #Christoph Forster , but the input File was not getting copied anywhere in the workspace .
So I used the workaround as provided in
https://bitbucket.org/janvrany/jenkins-27413-workaround-library/src/6b7dada8ea37?at=default
The library provides a new library - unstashParam - that saves the file build parameter into a workspace. Works fine with text and yaml file .
I also tried using the solution by #Christoph Forster but I received a script security error when Groovy Sandbox is enable
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use new hudson.FilePath java.io.File
However, it seems we can skip the file copying and deleting actions (and bypass the Groovy sandbox restriction) by simply requiring that the file is uploaded to the job workspace. Just add the workspace variable to the file name as follows:
stage("upload") {
def inputFile = input message: 'Upload file', parameters: [file(name: "$workspace/data.zip")]
}
I found a solution in the form of a global library here:
https://bitbucket.org/janvrany/jenkins-27413-workaround-library/src/default/
It contains calls to inner methods of Jenkins which are deprecated (I guess).
So I made my own version like this:
import hudson.FilePath
import hudson.model.ParametersAction
import hudson.model.FileParameterValue
import hudson.model.Executor
def call(String name, String fname = null) {
def paramsAction = currentBuild.rawBuild.getAction(ParametersAction.class);
if (paramsAction == null) {
error "unstashParam: No file parameter named '${name}'"
}
for (param in paramsAction.getParameters()) {
if (param.getName().equals(name)) {
if (! param instanceof FileParameterValue) {
error "unstashParam: not a file parameter: ${name}"
}
if (env['NODE_NAME'] == null) {
error "unstashParam: no node in current context"
}
if (env['WORKSPACE'] == null) {
error "unstashParam: no workspace in current context"
}
workspace = new FilePath(getComputer(env['NODE_NAME']), env['WORKSPACE'])
filename = fname == null ? param.getOriginalFileName() : fname
file = workspace.child(filename)
file.copyFrom(param.getFile())
return filename;
}
}
}
def getComputer(name){
for(computer in Jenkins.getInstance().getComputers()){
if(computer.getDisplayName() == name){
return computer.getChannel()
}
}
error "Cannot find computer for file parameter workaround"
}
You can insert it in a global library and then use it like:
library "file-workaround"
node {
def file_in_workspace = unstashParam "myFile"
sh "cat ${file_in_workspace}"
}
It's not pretty but it's working and as long as there is no official fix, it's my best shot.
Update
Turns out you might run into "No such file or directory". That's because nothing in the workaround triggers Jenkins to create the workspace directory. If that was triggered somewhere else in the pipeline good, otherwise you'll be scratching your head.
You might wanna throw a
touch "thisIsAFile"
in there
To handle an optional file parameter in pipeline (to handle the use case where no file should be accepted) you could use jenkinsci-unstashParam-library (add it in Jenkins>Manage Jenkins>Configure System>Global Pipeline Libraries https://github.com/janvrany/jenkinsci-unstashParam-library) with a try/catch in a script as this sample stage:
stage('upload') {
steps {
// delete workspace
cleanWs()
// handle file parameters in pipeline (JENKINS-27413)
script {
try {
// force workspace directory creation
sh "touch emptyFileToCreateWorkspace"
// https://stackoverflow.com/questions/59468464/fetching-uploaded-files-in-jenkins
def file_in_workspace = unstashParam 'MY_FILE.xlsx'
// https://unix.stackexchange.com/questions/125776/error-with-a-file-name-containing-parentheses
sh "mv '${file_in_workspace}' MY_FILE.xlsx"
}
catch (Exception e) {
echo e.getMessage()
echo "No file parameter, we will continue.."
}
}
}
}
File parameters provides 2 alternative parameters types for files (stashed for large files and base64 for small files).
Example, for base64File:
node {
sh 'echo $FILE | base64 -d'
withFileParameter('FILE') {
sh 'cat $FILE'
}
}
and stashedFile:
node {
unstash 'FILE'
sh 'cat FILE'
}
Tried what Christoph suggested and it didnt work for me. Here is what worked for me and the setup which I have, his should help others figure out what to do.
Problem:
I am executing my pipeline on dedicated nodes and use sanitized workspaces. After some research and troubleshooting I found out that by default the file upload only works with Master node. I realized this after digging through the file system and finding the file I am uploading in the workspace on the master
Solution:
stage('Upload Key') {
agent { label 'master' }
steps {
script {
// Uploads file via master node and stases it for other nodes to access
def inputFile = input message: 'Upload file', parameters: [file(name: "key.p12")]
new hudson.FilePath(new File("${workspace}/key.p12")).copyFrom(inputFile)
inputFile.delete()
}
stash name: 'key.p12' , includes: "key.p12"
}
}
stage('Register') {
steps {
ws (sanitizedWorkspaceName) {
echo "Registering"
unstash 'key.p12'
}
}
}
Execute the suggested file copy solution by Christoph. This stores the file in the job workspace on the master node
Allow the scripts in Manage Jenkins > In Process Script approval
use the stash step to stash the uploaded file
In the target stage "running on a different node" use the unstash
Hope this helps
I wasn't able to make Christoph's solution working if the file was uploaded on master node and needed on slave. The solution was to stash it on master and later unstash it on slave. Don't forget to remove the uploaded file on master node.
It's supported by the latest File Parameters plugin now. Please refer to: How to pass a file parameter to another build job in jenkins pipeline?