Search code examples
jenkinsjenkins-pipelinejenkins-declarative-pipeline

How to block upstream/downstream build in Jenkins declarative pipeline?


I have 3 downstream build jobs which are triggered when 1 upstream job 'Project U' has been built successfully. Example:

    triggers {
        pollSCM('H/5 * * * *')
        upstream(upstreamProjects: 'Project U', threshold: hudson.model.Result.SUCCESS)
    }    

This works as expected, however, if code changes are committed to all parts at the same time, the upstream and downstream builds start building simultaneously.

I want to avoid this, because the downstream builds will run twice, and the first run is quite useless as the upstream commit has not been built yet. So I would like to configure the downstream jobs to block their build while the upstream job is building.

I know how to do this in Jenkins Freestyle job in the user interface (also see this answer):

But I cannot find how to do this in a Jenkins declarative pipeline?


Solution

  • This approach works:

            waitUntil {
                def job = Jenkins.instance.getItemByFullName("Project U")
                !job.isBuilding() && !job.isInQueue()
            }
    

    When this downstream is started, it will check if the upstream job is either active or queued, and if so, will wait until its build has finished.

    I haven't been able to find out how to programmatically access the current job's upstream job(s), so the job names need to be copied. (There is a method getBuildingUpstream(), which would be much more convenient, but I haven't found a way to obtain the current Project object from the Job instance.)

    I finally ended up creating this function in the Jenkins shared library:

    /*
        vars/waitForJobs.groovy
        
        Wait until none of the upstream jobs is building or being queued any more
    
        Parameters:
        
        upstreamProjects   String with a comma separated list of Jenkins jobs to check 
                           (use same format as in upstream trigger) 
        
    */
    
    def call( Map params) {
    
        projects = params['upstreamProjects'].split(', *')
        
        echo 'Checking the following upstream projects:' 
        if ( projects.size() == 0) {
            echo 'none'
        } else {
            projects.each {project ->
                echo "${project}"
            }
        }
        
        waitUntil {
            def running = false
            projects.each {project ->
                def job = Jenkins.instance.getItemByFullName(project)
                if (job == null) {
                    error "Project '${project} not found"
                }
                if (job.isBuilding()) {
                    echo "Waiting for ${project} (executing)"
                    running = true             
                }
                if (job.isInQueue()) {
                    echo "Waiting for ${project} (queued for execution)"
                    running = true
                }
            }
            return !running
        }
    
    }
    

    The nice thing is that I can just copy the parameter from the upstream trigger, because it uses the exact same format. Here's an example of how it looks like:

    pipeline {
    [...]   
        triggers {
            pollSCM('H/5 * * * *')
            upstream(upstreamProjects: 'Project U1, Project U2, Project U3', threshold: hudson.model.Result.SUCCESS)
        }    
    [...]
        stages {
            stage('Wait') {
                steps {
                    script{
                        // Wait for upstream jobs to finish
                        waitForJobs(upstreamProjects: 'Project U1, Project U2, Project U3')
                    }
                }
    [...]
            }
        }
    }