Chef - creating a cronjob and testing it with Inspec - cron

I'm trying to set up a cronjob to run a command (in this example ls) once every day. For this I am using the cron resource.
The problem is that I don't know how to test it with Inspect. I tried using crontab but it's not working.
Here's the code:
// code
cron 'my-ls' do
minute '1'
hour '0'
command 'ls'
end
// test
describe crontab.commands('ls') do
its('minutes') { should cmp '1' }
its('hours') { should cmp '0' }
end
It's failing saying that:
× hours should cmp == "0"
expected: "0"
got: []
(compared using `cmp` matcher)
× minutes should cmp == "1"
expected: "1"
got: []
(compared using `cmp` matcher)
PS: I also tried with cron_d using the cron cookbook

Here's the simplest way I could get these tests working:
Step 1: Create a text file named cronls.txt with this data:
1 0 * * * ls -al
Step 2: Turn it into a cron job with this command:
crontab -a cronls.txt
Step 3: In your Chef cookbook, add these controls to your default_test.rb:
control 'cron-1' do
describe crontab do
its('commands') { should include 'ls -al' }
end
end
control 'cron-2' do
describe crontab.commands('ls -al') do
its('minutes') { should cmp '1' }
its('hours') { should cmp '0' }
end
end
Step 4: Execute your InSpec tests:
inspec exec test/integration/default/default_test.rb
The results are what you would expect:
✔ cron-1: crontab for current user
✔ crontab for current user commands should include "ls -al"
✔ cron-2: crontab for current user with command == "ls -al"
✔ crontab for current user with command == "ls -al" minutes should cmp == "1"
✔ crontab for current user with command == "ls -al" hours should cmp == "0"
This isn't the only way to do this (or even the best way), but it should get you going. For more options on the crontab resource, read the InSpec documentation:
https://docs.chef.io/resource_cron.html

Related

Running crontab only on one line in a file each time

I'm trying to configure crontab to execute at different times different lines of code inside a file. I basically have a bash script file that starts some java -jar. The problem is that each line should be executed at a different time. I can configure crontab to run the whole script at different times but no the lines to run. Now this is important that the bash file will stay only one file and not broken down to a few files.
Thanks!
One way of doing it (via command line arguments passed by cron)
some_script.sh:
if test $1 = 1 ; then
# echo "1 was entered"
java -jar some_file.jar
elif test $1 = 2 ; then
# echo "2 was entered"
java -jar another_file.jar
fi
crontab example:
* 1 * * * /bin/bash /home/username/some_script.sh 1
* 2 * * * /bin/bash /home/username/some_script.sh 2
Another approach (hour matching done in bash script)
some_script.sh:
hour=$(date +"%H");
if test $hour = 1 ; then
# echo "the hour is 1";
java -jar some_file.jar
elif test $hour = 2 ; then
# echo "the hour is 2";
java -jar another_file.jar
fi
crontab example:
* 1 * * * /bin/bash /home/username/some_script.sh
* 2 * * * /bin/bash /home/username/some_script.sh

How do I get the output of a shell command executed using into a variable from Jenkinsfile (groovy)?

I have something like this on a Jenkinsfile (Groovy) and I want to record the stdout and the exit code in a variable in order to use the information later.
sh "ls -l"
How can I do this, especially as it seems that you cannot really run any kind of groovy code inside the Jenkinsfile?
The latest version of the pipeline sh step allows you to do the following;
// Git committer email
GIT_COMMIT_EMAIL = sh (
script: 'git --no-pager show -s --format=\'%ae\'',
returnStdout: true
).trim()
echo "Git committer email: ${GIT_COMMIT_EMAIL}"
Another feature is the returnStatus option.
// Test commit message for flags
BUILD_FULL = sh (
script: "git log -1 --pretty=%B | grep '\\[jenkins-full]'",
returnStatus: true
) == 0
echo "Build full flag: ${BUILD_FULL}"
These options where added based on this issue.
See official documentation for the sh command.
For declarative pipelines (see comments), you need to wrap code into script step:
script {
GIT_COMMIT_EMAIL = sh (
script: 'git --no-pager show -s --format=\'%ae\'',
returnStdout: true
).trim()
echo "Git committer email: ${GIT_COMMIT_EMAIL}"
}
Current Pipeline version natively supports returnStdout and returnStatus, which make it possible to get output or status from sh/bat steps.
An example:
def ret = sh(script: 'uname', returnStdout: true)
println ret
An official documentation.
quick answer is this:
sh "ls -l > commandResult"
result = readFile('commandResult').trim()
I think there exist a feature request to be able to get the result of sh step, but as far as I know, currently there is no other option.
EDIT: JENKINS-26133
EDIT2: Not quite sure since what version, but sh/bat steps now can return the std output, simply:
def output = sh returnStdout: true, script: 'ls -l'
If you want to get the stdout AND know whether the command succeeded or not, just use returnStdout and wrap it in an exception handler:
scripted pipeline
try {
// Fails with non-zero exit if dir1 does not exist
def dir1 = sh(script:'ls -la dir1', returnStdout:true).trim()
} catch (Exception ex) {
println("Unable to read dir1: ${ex}")
}
output:
[Pipeline] sh
[Test-Pipeline] Running shell script
+ ls -la dir1
ls: cannot access dir1: No such file or directory
[Pipeline] echo
unable to read dir1: hudson.AbortException: script returned exit code 2
Unfortunately hudson.AbortException is missing any useful method to obtain that exit status, so if the actual value is required you'd need to parse it out of the message (ugh!)
Contrary to the Javadoc https://javadoc.jenkins-ci.org/hudson/AbortException.html the build is not failed when this exception is caught. It fails when it's not caught!
Update:
If you also want the STDERR output from the shell command, Jenkins unfortunately fails to properly support that common use-case. A 2017 ticket JENKINS-44930 is stuck in a state of opinionated ping-pong whilst making no progress towards a solution - please consider adding your upvote to it.
As to a solution now, there could be a couple of possible approaches:
a) Redirect STDERR to STDOUT 2>&1
- but it's then up to you to parse that out of the main output though, and you won't get the output if the command failed - because you're in the exception handler.
b) redirect STDERR to a temporary file (the name of which you prepare earlier) 2>filename (but remember to clean up the file afterwards) - ie. main code becomes:
def stderrfile = 'stderr.out'
try {
def dir1 = sh(script:"ls -la dir1 2>${stderrfile}", returnStdout:true).trim()
} catch (Exception ex) {
def errmsg = readFile(stderrfile)
println("Unable to read dir1: ${ex} - ${errmsg}")
}
c) Go the other way, set returnStatus=true instead, dispense with the exception handler and always capture output to a file, ie:
def outfile = 'stdout.out'
def status = sh(script:"ls -la dir1 >${outfile} 2>&1", returnStatus:true)
def output = readFile(outfile).trim()
if (status == 0) {
// output is directory listing from stdout
} else {
// output is error message from stderr
}
Caveat: the above code is Unix/Linux-specific - Windows requires completely different shell commands.
this is a sample case, which will make sense I believe!
node('master'){
stage('stage1'){
def commit = sh (returnStdout: true, script: '''echo hi
echo bye | grep -o "e"
date
echo lol''').split()
echo "${commit[-1]} "
}
}
For those who need to use the output in subsequent shell commands, rather than groovy, something like this example could be done:
stage('Show Files') {
environment {
MY_FILES = sh(script: 'cd mydir && ls -l', returnStdout: true)
}
steps {
sh '''
echo "$MY_FILES"
'''
}
}
I found the examples on code maven to be quite useful.
All the above method will work. but to use the var as env variable inside your code you need to export the var first.
script{
sh " 'shell command here' > command"
command_var = readFile('command').trim()
sh "export command_var=$command_var"
}
replace the shell command with the command of your choice. Now if you are using python code you can just specify os.getenv("command_var") that will return the output of the shell command executed previously.
How to read the shell variable in groovy / how to assign shell return value to groovy variable.
Requirement : Open a text file read the lines using shell and store the value in groovy and get the parameter for each line .
Here , is delimiter
Ex: releaseModule.txt
./APP_TSBASE/app/team/i-home/deployments/ip-cc.war/cs_workflowReport.jar,configurable-wf-report,94,23crb1,artifact
./APP_TSBASE/app/team/i-home/deployments/ip.war/cs_workflowReport.jar,configurable-temppweb-report,394,rvu3crb1,artifact
========================
Here want to get module name 2nd Parameter (configurable-wf-report) , build no 3rd Parameter (94), commit id 4th (23crb1)
def module = sh(script: """awk -F',' '{ print \$2 "," \$3 "," \$4 }' releaseModules.txt | sort -u """, returnStdout: true).trim()
echo module
List lines = module.split( '\n' ).findAll { !it.startsWith( ',' ) }
def buildid
def Modname
lines.each {
List det1 = it.split(',')
buildid=det1[1].trim()
Modname = det1[0].trim()
tag= det1[2].trim()
echo Modname
echo buildid
echo tag
}
If you don't have a single sh command but a block of sh commands, returnstdout wont work then.
I had a similar issue where I applied something which is not a clean way of doing this but eventually it worked and served the purpose.
Solution -
In the shell block , echo the value and add it into some file.
Outside the shell block and inside the script block , read this file ,trim it and assign it to any local/params/environment variable.
example -
steps {
script {
sh '''
echo $PATH>path.txt
// I am using '>' because I want to create a new file every time to get the newest value of PATH
'''
path = readFile(file: 'path.txt')
path = path.trim() //local groovy variable assignment
//One can assign these values to env and params as below -
env.PATH = path //if you want to assign it to env var
params.PATH = path //if you want to assign it to params var
}
}
Easiest way is use this way
my_var=`echo 2`
echo $my_var
output
: 2
note that is not simple single quote is back quote ( ` ).

bash function return status

I have a function
function f() {
command 1
command 2
command 3
command 4
}
I want function f() to somehow tell me there is an error if any of the 4 commands fails.
I also don't want to set -e. I want four commands all run, even if one fails.
How do I do that? Sorry for the newbie question -- I am quite new to bash scripting.
If I understand what you're asking correctly, you can do this:
f() {
err=""
command 1 || err=${err}1
command 2 || err=${err}2
command 3 || err=${err}3
command 4 || err=${err}4
# do something with $err to report the error
}
Of course, instead of using a variable you could simply put echo commands after the || if all you need to do is print an error message:
f() {
command 1 || echo "command 1 failed" >&2
command 2 || echo "command 2 failed" >&2
command 3 || echo "command 3 failed" >&2
command 4 || echo "command 4 failed" >&2
}
Take advantage of "$#" and write a higher-order function:
function warner () { "$#" || echo "Error when executing '$#'" >&2; }
Then:
warner command 1
warner command 2
warner command 3
warner command 4
Test:
$ warner git status
fatal: Not a git repository (or any of the parent directories): .git
Error when executing 'git status'
$ warner true
As #user1261959 found out, this is the same approach as in this answer.
You can check the exit flag of any of your commands after they run by checking the variable $?.If it returns 0 usually everything went well otherwise it means an error occurred. You can make your function return a flag to the caller by using the keyword return

Whiptail Gauge: Variable in loop not being set

Am new to bash and whiptail so excuse the ignorance.
When assigning a var in the for loop, the new value of 20 is never set when using a Whiptail dialog. Any suggestions why ?
andy="10"
{
for ((i = 0 ; i <= 100 ; i+=50)); do
andy="20"
echo $i
sleep 1
done
} | whiptail --gauge "Please wait" 5 50 0
# }
echo "My val $andy
A command inside a pipeline (that is, a series of commands separated by |) is always executed in a subshell, which means that each command has its own variable environment. The same is true of the commands inside the compound command (…), but not the compound command {…}, which can normally be used for grouping without creating a subshell.
In bash or zsh, you can solve this problem using process substitution instead of a pipeline. For example:
andy="10"
for ((i=0 ; i <= 100 ; i+=50)); do
andy="20"
echo $i
sleep 1
done > >(whiptail --gauge "Please wait" 6 50 0)
echo "My val $andy
>(whiptail ...) will cause a subshell to be created to execute whiptail; the entire expression will be substituted by the name of this subshell's standard input (in linux, it will be something like /dev/fd/63, but it could be a FIFO on other OSs). > >(...) causes standard output to be redirected to the subshell's standard input; the first > is just a normal stdout redirect.
The statements inside {} are not ordinarily executed in a sub-shell. However, when you add a pipe (|) to it, they seem to be executed in a sub-shell.
If you remove the pipe to whiptail, you will see the update value of andy.

Show job count in bash prompt only if nonzero

A typical prompt in bash in something like:
PS1="\u#\h:\w\$ "
The you can show the number of background jobs using \j, e.g.:
PS1="\u#\h:\w [\j]\$ "
Which is useful, because every now and then I forget I have a stopped job and only notice when it complains if I manually logout from the shell.
However, 95% of the time, the background job count is 0 and showing it in the prompt is superfluous.
How can I show the job count in the prompt, but only if it's nonzero?
You can e.g. do something like this:
PS1='\u#\h:\w $([ \j -gt 0 ] && echo [\j])\$ '
The accepted answer does not work for me (I have got Bash v4.2.46). It throws an error like this:
[: \j: integer expression expected
I had to use PROMPT_COMMAND to achieve the same functionality:
export PROMPT_COMMAND=__prompt_command
function __prompt_command() {
local JOBS=$(jobs | wc -l | tr -d 0)
PS1="\u#\h:\w [${JOBS}]\$ "
}

Resources