Grails script and passing parameters using Groovy CLIBuilder? - grails

I have created a very simple script and would like to pass arguments to the script.
like:
grails> helloworld -n Howdy
grails> helloworld -name Howdy
with the script:
target(main: 'Hello World') {
def cli = new CliBuilder()
cli.with
{
h(longOpt: 'help', 'Help - Usage Information')
n(longOpt: 'name', 'Name to say hello to', args: 1, required: true)
}
def opt = cli.parse(args)
if (!opt) return
if (opt.h) cli.usage()
println "Hello ${opt.n}"
}
I seem to fail in every attempt that i do. The script keeps complain about the -n option being not present.
When i debug the script the value op the args parameters looks like the values are rearranged.
When calling the script with :
grails> helloworld -n Howdy
the value of args inside the script is: Howdy -n
What am i missing here of doing wrong? Any suggestions?

Your problem is that you're running your code through grails shell. I've converted your code to CLI.groovy like this:
class CLI{
public static void main(String [] args){
def cli = new CliBuilder()
cli.with
{
h(longOpt: 'help', 'Help - Usage Information')
n(longOpt: 'name', 'Name to say hello to', args: 1, required: true)
}
def opt = cli.parse(args)
if (!opt) return
if (opt.h) cli.usage()
println "Hello ${opt.n}"
}
}
After that I'm using groovy command to run it from linux shell like that:
archer#capitan $ groovy CLI -n Daddy
It outputs:
archer#capitan $ groovy CLI -n Daddy
Hello Daddy
So it works like a charm.

I did a Google search for site:github.com grailsScript CliBuilder and came across:
https://github.com/Grails-Plugin-Consortium/grails-cxf/blob/master/scripts/WsdlToJava.groovy
That gave me the hint that the args variable needs to be formatted. Unfortunately it mutates -n Howdy into Howdy\n-n (not sure why the order is rearranged or the newline character is added).
The github page above has a doSplit() method to handle some of this, but it keeps the rearranged order. The best thing I've found is to remove the space between -n and Howdy, which will work with CliBuilder.
The following is what I have working:
target(main: 'Hello World') {
def cli = new CliBuilder()
cli.with
{
h(longOpt: 'help', 'Help - Usage Information')
n(longOpt: 'name', 'Name to say hello to', args: 1, required: true)
}
def ops = doSplit(args)
def opt = cli.parse(ops)
if (!opt) return
if (opt.h) cli.usage()
println "Hello ${opt.n}"
}
private doSplit(String string){
string.split(/(\n|[ ]|=)/).collect{ it.trim() }.findResults { it && it != '' ? it : null }
}
Run this with: helloworld -nHowdy

Related

Possible to run multiline shell command/string in groovy/gradle?

In gradle 7 I have created this method:
def shellCmd(String cmd) {
exec {
executable "sh"
args "-c", cmd
}
}
And this "pure" groovy version:
def shellCmd2(String cmd) {
def process = cmd.execute()
def output = new StringWriter(), error = new StringWriter()
process.waitForProcessOutput(output, error)
}
Which I call from another method e.g.:
def myMethod() {
shellCmd('ls -la')
}
I am now experimenting with getting it to work with multi-line (jenkins like) shell commands:
def myMethod() {
def cmd = """
for i in \$(ls -la); do
if [[ \$i == 'settings.gradle' ]]; then
echo "Found $i"
fi
done
"""
shellCmd(cmd)
}
but it fails with:
script '/home/user/samples/build.gradle': 47: Unexpected input: 'i' # line 47, column 5.
for i in $(ls -la); do
^
1 error
Probably I am breaking all the rules here but any input?
Also tried most of the suggestions here:
What's wrong with Groovy multi-line String?
but no luck so far.
Also based on the suggestion below I have tried to use shellCmd2 method (I am leaning towards using a plain groovy method for this to make it easier to debug outside of gradle) with:
def myMethod() {
def cmd = """
for i in \$(ls -la); do
if [ \$i = 'settings.gradle' ]; then
echo "Found \$i"
fi
done
"""
shellCmd2(cmd)
}
But that gives:
Caught: java.io.IOException: Cannot run program "for": error=2, No such file or directory
java.io.IOException: Cannot run program "for": error=2, No such file or directory
So seems the for keyword is now causing an issue.
There is nothing wrong with your multiline string. I had to change three things to make your code work:
Use single brackets [ and ]
Use single equal sign (=)
Escape the missing $i variable which was being interpreted as a Groovy variable.
You are using sh so you should only use POSIX compatible features.
Code:
def shellCmd(String cmd) {
exec {
executable "sh" // or use another shell like bash or zsh (less portable)
args "-c", cmd
}
}
def myMethod() {
def cmd = """
for i in \$(ls -la); do
if [ \$i = 'settings.gradle' ]; then
echo "Found \$i"
fi
done
"""
shellCmd(cmd)
}
You can compare with double brackets with shells like zsh and bash.

Access a groovy variable from within shell in Jenkins pipeline

This answer did not help me
Below is the method in groovy:
def analyze(repoName){
result= sh (
script: '''
cd ${WORKSPACE}/${BUILD_NUMBER}
cat > sonar-project.properties << EOF_$$
sonar.projectKey=ABC-$repoName
sonar.projectName=ABC
sonar.projectBaseDir=${WORKSPACE}/${BUILD_NUMBER}
EOF_$$
''',
returnStatus: true
) == 0
print "Creating file - Return status: ${result}"
}
where below line gives error:
sonar.projectKey=ABC-$repoName
properties file gets created with entry sonar.projectKey=ABC-
How to use groovy variable in sh() step?
You should double quotes for string interpolation and escape $ by \$ in following places:
${WORKSPACE} and ${BUILD_NUMBER}, you intent to use them as bash environment variable, rather than groovy variable
EOF_$$, you intent to use it literal meaning
Changed code:
def analyze(repoName){
result= sh (
script: """
cd \${WORKSPACE}/\${BUILD_NUMBER}
cat > sonar-project.properties << EOF_\$\$
sonar.projectKey=ABC-$repoName
sonar.projectName=ABC
sonar.projectBaseDir=\${WORKSPACE}/\${BUILD_NUMBER}
EOF_\$\$
""",
returnStatus: true
) == 0
print "Creating file - Return status: ${result}"
}
You should use double quotes for string interpolation, so just replace '''with """
And change EOF_$$ to EOF_\$\$

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
}

Groovy code for reading a key:value from a text file

I have a config file config.txt with following key:values
a=1,2,3
b=5,6,7
I want to read the keys a nd b using groovy script but its giving following error message:
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods withInputStream java.io.File groovy.lang.Closure
The code is as under:
Properties properties = new Properties()
File propertiesFile = new File('config.txt')
propertiesFile.withInputStream {
properties.load(it)
}
def runtimeString = 'a'
assert properties."$runtimeString" == '1'
assert properties.b == '2'
What am I missing?
Pipeline DSL context runs on master node even that your write node('someAgentName') in your pipeline. new File will work only on master.
But you can read data from file via sh(). Something like:
def a = sh(returnStdout: true, script: "cat config.txt | grep a | cut -f2 -d'='").trim()
def b = sh(returnStdout: true, script: "cat config.txt | grep b | cut -f2 -d'='").trim()
I tested the following in the Groovy console and the assertions pass
new File('config.txt').withReader {
def props = new Properties()
props.load(it)
assert props.getProperty('a') == '1,2,3'
assert props.getProperty('b') == '5,6,7'
}

Jenkins pipeline: Running a shell command returns "Bad substitution", but why?

I'd like to populate the the groovy variable "committer" with the output of the command:
def committer = utils.sh("curl -s -u \${J_USER}:\${J_PASS} \${env.BUILD_URL}/api/json | python -mjson.tool | grep authorEmail | awk '{print \$2}' | tr -d '"|,' ")
Because of the known issue in Jenkins (JENKINS-26133) it is not possible to do that but only to populate the variable with the exit status of the command.
So I've go these 2 functions:
def gen_uuid(){
randomUUID() as String
}
def sh_out(cmd){ // As required by bug JENKINS-26133
String uuid = gen_uuid()
sh """( ${cmd} )> ${uuid}"""
String out = readFile(uuid).trim()
sh "set +x ; rm ${uuid}"
return out
}
These functions allow me to wrap my shell commands in sh_out(COMMAND) and in the background I'm using the workaround which is suggested in the mentioned above known issue link which means running the command while redirecting it's output to a file (in the case of my function it's a random filename) and then reading it into a variable.
So, In the beginning of my pipeline I load my functions file which ends with return this; like so:
fileLoader.withGit('git#bitbucket.org:company/pipeline_utils.git', 'master', git_creds, ''){
utils = fileLoader.load('functions.groovy');
}
And that's why the "utils.sh_out" that you see in the command, but when I use the shown above command in my Jenkins pipeline, I get the following error:
/home/ubuntu/workspace/-6870-bitbucket-integration-ECOPKSSBUJ6HCDNM4TOY77X7UTZ#tmp/durable-006d5c7e/script.sh: 2: /home/ubuntu/workspace/-6870-bitbucket-integration-ECOPKSSBUJ6HCDNM4TOY77X7UTZ#tmp/durable-006d5c7e/script.sh: Bad substitution
Running the command in a shell works properly:
$ curl -s -u user:password http://IPADDR:8080/job/COMPANY_BitBucket_Integration/job/research/job/COMPANY-6870-bitbucket-integration/3/api/json/api/json | python -mjson.tool | grep authorEmail | awk '{print $2}' | tr -d '"|,'
user#email.com
I suspect it has something to do with the tr command in the end and with the character escaping I did there but whatever I try fails, anyone got an idea?
according to the documentation now sh supports std output.
and i know i'm not answering your question directly, but i suggest to use groovy to parse json.
you are trying to get the value of authorEmail from json
if the response from /api/json looks like this (just an example):
{
"a":{
"b":{
"c":"ccc",
"authorEmail":"user#email.com"
}
}
}
then the groovy to take athorEmail:
def cmd = "curl -s -u \${J_USER}:\${J_PASS} \${env.BUILD_URL}/api/json"
def json = sh(returnStdout: true, script: cmd).trim()
//parse json and access it as an object (Map/Array)
json = new groovy.json.JsonSlurper().parseText(json)
def mail = json.a.b.athorEmail
you could receive java.io.NotSerializableException explained here
so i changed the code like this:
node {
def json = sh(
returnStdout: true,
script: "curl -s -u \${J_USER}:\${J_PASS} \${env.BUILD_URL}/api/json"
).trim()
def mail = evaluateJson(json, '${json.a.b.authorEmail}')
echo mail
}
#NonCPS
def evaluateJson(String json, String gpath){
//parse json
def ojson = new groovy.json.JsonSlurper().parseText(json)
//evaluate gpath as a gstring template where $json is a parsed json parameter
return new groovy.text.GStringTemplateEngine().createTemplate(gpath).make(json:ojson).toString()
}

Resources