Issue
How do I add an input step, with timeout, that continues if timeout is reached, using a default value, in a Pipeline job?
Environment
- CloudBees Jenkins Enterprise
- Pipeline plugin
Resolution
You can use a try
catch
block to achieve this.
The following asks for input, with a timeout of 15 seconds. If the timeout is reached the default is true. An if statement checking the input result (userInput) is used to determine what to do next:
def userInput = true
def didTimeout = false
try {
timeout(time: 15, unit: 'SECONDS') { // change to a convenient timeout for you
userInput = input(
id: 'Proceed1', message: 'Was this successful?', parameters: [
[$class: 'BooleanParameterDefinition', defaultValue: true, description: '', name: 'Please confirm you agree with this']
])
}
} catch(err) { // timeout reached or input false
def user = err.getCauses()[0].getUser()
if('SYSTEM' == user.toString()) { // SYSTEM means timeout.
didTimeout = true
} else {
userInput = false
echo "Aborted by: [${user}]"
}
}
node {
if (didTimeout) {
// do something on timeout
echo "no input was received before timeout"
} else if (userInput == true) {
// do something
echo "this was successful"
} else {
// do something else
echo "this was not successful"
currentBuild.result = 'FAILURE'
}
}
Reference:
Comments
8 comments
The issue with this is that when a user clicks the X button to cancel the build outside of the input rejection. I see that the user in the cause is actually SYSTEM rather than the user that cancelled it.
What we see when the build is clicked through the X button in the running builds list. The log even says "Aborted by admin" but the user on the cause is actually SYSTEM. This effectively means that there is no good way to tell a timeout apart from a user abort outside of the input.
Vs the Timeout
I guess it is a weird requirement to actually want to proceed if the input times out rather than dying. If someone else has this requirement one day, we used this hack. :)
userAborted = false
startMillis = System.currentTimeMillis()
timeoutMillis = 10000
try {
timeout(time: timeoutMillis, unit: 'MILLISECONDS') {
input 'Do you approve?'
}
} catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
cause = e.causes.get(0)
echo "Aborted by " + cause.getUser().toString()
if (cause.getUser().toString() != 'SYSTEM') {
startMillis = System.currentTimeMillis()
} else {
endMillis = System.currentTimeMillis()
if (endMillis - startMillis >= timeoutMillis) {
echo "Approval timed out. Continuing with deployment."
} else {
userAborted = true
echo "SYSTEM aborted, but looks like timeout period didn't complete. Aborting."
}
}
}
if (userAborted) {
currentBuild.result = 'ABORTED'
} else {
currentBuild.result = 'SUCCESS'
echo "Firing the missiles!"
}
Hi Ben,
Thank you for clarification and a workaround. Have you considered raising an Improvement request for this.
https://wiki.jenkins-ci.org/display/JENKINS/How+to+report+an+issue
https://issues.jenkins-ci.org/
Oh hi there Denys, fancy meeting you here! :)
I held off submitting an issue because I thought that my use case was a little weird, but if you think that it's worthwhile submitting one I will do so.
Cheers,
Ben
hi, sir and madam,
is it possible to
thank you very much!
Ben,
I have this same use-case. Using your "hack" for now.
A slightly (but only barely) better solution is to ask for the class of the cause, rather than the user:
Hi,
I tried this working but it is blocking agent at manual input, do we have any solution to fix (not to block agent during manual input)
Please sign in to leave a comment.