The milestone plugin always feels on the surface like the perfect solution for many CI/CD pipelines my team works with. The idea of aborting old pipeline instances if a new one surpasses it feels right. In practice, however, we are often waiting for something outside of the pipeline's control; something like a scheduled release time. This often results in some kind of polling loop or `waitUntil` step to wait for that scheduled time.
If two executions are running, and waiting at a polling stage, the milestone step breaks down. The ordinal value for the milestone is not allowed to be repeated, so placing the milestone step within a waitUntil or other loop causes the following error:
ERROR: Invalid ordinal 1, as the previous one was 1
Repeating the ordinal FEELS right in this situation, but perhaps not. Any alternative approach I've come up with is either very complex, or doesn't resolve the problem consistently. I've attempted a number of things including the lock step, incrementing the ordinal with a variable, and simply moving it to after the loop, and some declarative tries. Any approach I take appears to take me further from my goal of simply aborting all earlier executions that have made it to a specific stage.
My suggestion would be to add a new argument (perhaps `lastOrdinalRepeatable: true`). If the milestone is passed a second time it should abort the job if any newer executions have passed (just like it would have on the first run through).
I'm open to other approaches. I've come back to this problem over and over and it seems like a reasonable use case.
Attached at the end is an example pipeline describing the behavior comment in the desired milestone line for a few variations.
pipeline{ agent none stages{ stage('build'){ steps { sleep time: 3, unit: 'SECONDS' } } stage('wait'){ steps{ echo "${currentBuild.timeInMillis}" timeout(time: 1, unit: 'DAYS') { waitUntil{ script{ echo "${currentBuild.timeInMillis}+${currentBuild.duration}" // The following milestone step causes an error: // ERROR: Invalid ordinal 1, as the previous one was 1 //milestone(1) // Poor-man's logic to block till deployment time if ((currentBuild.timeInMillis+currentBuild.duration)<1602694800000) { return false } return true } } } // The following milestone step executes, but is inconsistent // in its results. (not all old executions are aborted) // it also executes the entire waitUntil, because the milestone // hasn't been passed till after waitUntil completes milestone(1) } } stage('deploy'){ steps{ echo "simulate deploy" } } } }
Below is a screen shot of that pipeline:
#95-96 - executed concurrently with the same "scheduled time", with the milestone commented in AFTER the waitUntil block
#98-97 - executed concurrently with the same "scheduled time", with the milestone commented in AFTER the waitUntil block - inconsistent results to previous test
#99-100 - executed concurrently with the same "scheduled time", with the milestone commented in WITHIN the waitUntil block, and fails with the expected ordinal error