├── features_view.groovy ├── generate-xml.sh ├── Jenkinsfile ├── sonarqube_view.groovy ├── infra_view.groovy ├── mainline_view.groovy ├── docker_cleanup_job.groovy ├── generator_job.groovy ├── a_template_job.groovy ├── LICENSE ├── README.md ├── features_job.groovy ├── mainline_job.groovy ├── sonarqube_job.groovy └── common.groovy /features_view.groovy: -------------------------------------------------------------------------------- 1 | listView("Feature Branches") { 2 | description "Show progress of Feature branch jobs" 3 | filterBuildQueue true 4 | jobs { 5 | regex '.*features' 6 | } 7 | columns { 8 | status() 9 | weather() 10 | name() 11 | lastDuration() 12 | lastSuccess() 13 | lastFailure() 14 | buildButton() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /generate-xml.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Set an environment variable GITLAB_JENKINS_TOKEN before starting this script. 4 | # Get your token from GitLab at https://git.example.com/profile/account 5 | # Use: 6 | # export GITLAB_JENKINS_TOKEN=your_token_here 7 | # 8 | # DO NOT CHANGE THIS LINE! Read above. 9 | export GITLAB_JENKINS_TOKEN="${GITLAB_JENKINS_TOKEN:=xxx_set_a_proper_gitlab_token_xxx}" 10 | 11 | mkdir -p `dirname $0`/xml 12 | cd `dirname $0`/xml 13 | ln -fs ../common.groovy . 14 | java -jar ../job-dsl-core.jar ../a_template_job.groovy ../*_job.groovy ../*_view.groovy 15 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!groovy 2 | 3 | node('master') { 4 | stage 'checkout' 5 | checkout scm 6 | 7 | stage 'job-dsl' 8 | withCredentials([ 9 | [$class: 'StringBinding', credentialsId: 'gitlab-api-token', variable: 'GITLAB_JENKINS_TOKEN'] 10 | ]) { 11 | // BUG: environment variable for GITLAB_JENKINS_TOKEN doesn't work with jobDsl 1.50 pipeline step 12 | jobDsl targets: [ '**/*_job.groovy', '**/*_view.groovy' ].join('\n'), 13 | removedJobAction: 'DELETE', 14 | removedViewAction: 'DELETE', 15 | lookupStrategy: 'JENKINS_ROOT' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sonarqube_view.groovy: -------------------------------------------------------------------------------- 1 | import common 2 | listView("SonarQube") { 3 | description "Show progress of SonarQube jobs" 4 | filterBuildQueue true 5 | jobs { 6 | regex '.*sonarqube$' 7 | // alternatively also: 8 | // common.projectsWithTag("jenkins-sonarqube", GITLAB_JENKINS_TOKEN).each { project -> 9 | // def project_name_canonical = project.path_with_namespace.replaceAll('/', '-') 10 | // name project_name_canonical 11 | // } 12 | } 13 | columns { 14 | status() 15 | weather() 16 | name() 17 | lastDuration() 18 | lastSuccess() 19 | lastFailure() 20 | // no need for this: buildButton() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /infra_view.groovy: -------------------------------------------------------------------------------- 1 | import common 2 | listView("Infra Projects") { 3 | description "Show a list of INFRA project jobs" 4 | filterBuildQueue true 5 | jobs { 6 | common.projectsWithTag("jenkins", GITLAB_JENKINS_TOKEN).each { project -> 7 | def project_name_canonical = project.path_with_namespace.replaceAll('/', '-') 8 | if (!(project_name_canonical ==~ /^infra.*/)) { 9 | return 10 | } 11 | name project_name_canonical 12 | } 13 | } 14 | columns { 15 | status() 16 | weather() 17 | name() 18 | lastDuration() 19 | lastSuccess() 20 | lastFailure() 21 | buildButton() 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /mainline_view.groovy: -------------------------------------------------------------------------------- 1 | import common 2 | listView("Mainline Branches") { 3 | description "Show progress of mainline branch jobs" 4 | filterBuildQueue true 5 | jobs { 6 | common.projectsWithTag("jenkins", GITLAB_JENKINS_TOKEN).each { project -> 7 | def project_name_canonical = project.path_with_namespace.replaceAll('/', '-') 8 | if (project_name_canonical ==~ /^infra.*/) { 9 | return 10 | } 11 | name project_name_canonical 12 | } 13 | } 14 | columns { 15 | status() 16 | weather() 17 | name() 18 | lastDuration() 19 | lastSuccess() 20 | lastFailure() 21 | buildButton() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docker_cleanup_job.groovy: -------------------------------------------------------------------------------- 1 | import common 2 | use(common) { 3 | job('docker-cleaner') { 4 | using 'tmpl_base' 5 | disabled false 6 | 7 | displayName 'Docker Cleaner' 8 | description 'Periodically clean stopped containers and untagged images' 9 | 10 | triggers { 11 | cron('H * * * *') // yes, every hour, but fizzy (not on minute 0) 12 | } 13 | 14 | steps { 15 | shell '''set -e 16 | |existed_containers=$(docker ps -aq --filter status=exited) 17 | |[ -n "$existed_containers" ] && docker rm $exited_containers 18 | |dangling_images=$(docker images -q --filter dangling=true) 19 | |[ -n "$dangling_images" ] && docker rmi $dangling_images 20 | |'''.stripMargin() 21 | } 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /generator_job.groovy: -------------------------------------------------------------------------------- 1 | import common 2 | use(common) { 3 | job('generator') { 4 | using 'tmpl_base' 5 | disabled false 6 | 7 | displayName 'Job Generator' 8 | description 'Generator of Jenkins Jobs' 9 | 10 | label 'master' 11 | 12 | scm { 13 | git { 14 | remote { 15 | credentials 'jenkins-gitlab-id' 16 | url 'git.example.com:infra/jenkins-jobs-generator.git' 17 | } 18 | browser { 19 | gitLab 'https://git.example.com/infra/jenkins-jobs-generator', '8.10' 20 | } 21 | } 22 | } 23 | 24 | triggers { 25 | scm('* * * * *') // yes, every minute. 26 | gitlabPush() 27 | } 28 | 29 | steps { 30 | dsl(['**/*_view.groovy', '**/*_job.groovy']) 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /a_template_job.groovy: -------------------------------------------------------------------------------- 1 | job('tmpl_base') { 2 | disabled true 3 | displayName 'Template: Base' 4 | description 'Template used for all other templates and projects configured using JobDSL' 5 | 6 | logRotator(-1, 10, -1, -1) 7 | compressBuildLog() 8 | 9 | wrappers { 10 | timestamps() 11 | colorizeOutput() 12 | } 13 | } 14 | 15 | job('tmpl_base_sonarqube') { 16 | using 'tmpl_base' 17 | displayName 'Template: SonarQube' 18 | description 'Template used for all other SonarQube projects configured using JobDSL' 19 | 20 | blockOnUpstreamProjects() 21 | concurrentBuild false 22 | } 23 | 24 | //** ... using 'tmpl_base_pipeline' - causes NullPointer exception in Jenkins only (not CLI) 25 | // workflowJob('tmpl_base_pipeline') { 26 | // disabled true 27 | // displayName 'Template: Base Pipeline' 28 | // description 'Template used for all other templates and projects configured using JobDsl' 29 | // 30 | // logRotator(-1, 10, -1, -1) 31 | // 32 | // wrappers { 33 | // timestamps() 34 | // colorizeOutput() 35 | // } 36 | // } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Prodops.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jenkins Job DSL 2 | 3 | All the Jenkins Job definitions in one place. 4 | 5 | Language reference: https://jenkinsci.github.io/job-dsl-plugin/ 6 | 7 | Visit the Jenkins server for the current API methods supported by the installed 8 | Plugin version: 9 | 10 | http:///plugin/job-dsl/api-viewer 11 | 12 | # Jenkins Jobs 13 | 14 | ## Generating jobs locally 15 | 16 | * clone https://github.com/jenkinsci/job-dsl-plugin.git 17 | 18 | * follow steps described at 19 | https://github.com/jenkinsci/job-dsl-plugin/wiki/User-Power-Moves#run-a-dsl-script-locally 20 | ( tl;dr: ./gradlew :job-dsl-core:oneJar && find job-dsl-core -name \*standalone.jar ) 21 | 22 | * move job-dsl-core/build/libs/job-dsl-core-1.xx-SNAPSHOT-standalone.jar 23 | to job-dsl-core.jar 24 | 25 | * test running the standalone jar using: 26 | java -jar job-dsl-core.jar 27 | 28 | Job-DSL is using Groovy, learn the basics at 29 | - https://learnxinyminutes.com/docs/groovy/ 30 | 31 | Job-DSL documentation & reference 32 | https://github.com/jenkinsci/job-dsl-plugin/wiki - documentation 33 | https://jenkinsci.github.io/job-dsl-plugin/ - reference 34 | -------------------------------------------------------------------------------- /features_job.groovy: -------------------------------------------------------------------------------- 1 | import common 2 | use(common) { 3 | common.projectsWithTag("jenkins-features", GITLAB_JENKINS_TOKEN).each { project -> 4 | def project_name_canonical = project.path_with_namespace.replaceAll('/', '-') 5 | 6 | multibranchPipelineJob(project_name_canonical + '-features') { 7 | displayName project.name_with_namespace + ' Features' 8 | description "Build job for feature/* branches of ${project.path_with_namespace}: ${project.description}" 9 | 10 | triggers { 11 | periodic 5 // every five minutes, check for branches 12 | } 13 | 14 | branchSources { 15 | git { 16 | credentialsId 'jenkins-gitlab-id' 17 | includes('feature/*') 18 | remote project.ssh_url_to_repo 19 | } 20 | } 21 | 22 | orphanedItemStrategy { 23 | discardOldItems { 24 | daysToKeep 6 25 | numToKeep 10 26 | } 27 | } 28 | 29 | } // workflowJob 30 | } // projectsWithTag.each 31 | } // use(common) 32 | -------------------------------------------------------------------------------- /mainline_job.groovy: -------------------------------------------------------------------------------- 1 | import common 2 | use(common) { 3 | common.projectsWithTag("jenkins", GITLAB_JENKINS_TOKEN).each { project -> 4 | def project_name_canonical = project.path_with_namespace.replaceAll('/', '-') 5 | 6 | pipelineJob(project_name_canonical) { 7 | // using 'tmpl_base_pipeline' - causes NullPointer exception in Jenkins only (not CLI) 8 | disabled false 9 | 10 | displayName project.name_with_namespace 11 | description "Build job for ${project.path_with_namespace}: ${project.description}" 12 | customWorkspace project.path_with_namespace 13 | 14 | triggers { 15 | gitlabPush() 16 | } 17 | 18 | definition { 19 | cpsScm { 20 | scriptPath "Jenkinsfile" 21 | scm { 22 | git { 23 | remote { 24 | credentials 'jenkins-gitlab-id' 25 | url project.ssh_url_to_repo 26 | branch project.default_branch 27 | } // remote 28 | browser { 29 | gitLab project.web_url, '8.10' 30 | } // browser 31 | } // git 32 | } // scm 33 | } // cpsScm 34 | } // definition 35 | } // workflowJob 36 | } // projectsWithTag.each 37 | } // use(common) 38 | -------------------------------------------------------------------------------- /sonarqube_job.groovy: -------------------------------------------------------------------------------- 1 | import common 2 | use(common) { 3 | common.projectsWithTag("jenkins-sonarqube", GITLAB_JENKINS_TOKEN).each { project -> 4 | def project_name_canonical = project.path_with_namespace.replaceAll('/', '-') 5 | 6 | job(project_name_canonical + '-sonarqube') { 7 | using 'tmpl_base_sonarqube' 8 | disabled false 9 | displayName project.name_with_namespace + ' SonarQube' 10 | description "Build job for SonarQube of ${project.path_with_namespace}: ${project.description}" 11 | scm { 12 | git { 13 | remote { 14 | credentials 'jenkins-gitlab-id' 15 | url project.ssh_url_to_repo 16 | branch project.default_branch 17 | } // remote 18 | browser { 19 | gitLab project.web_url, '8.10' 20 | } // browser 21 | } // git 22 | } // scm 23 | 24 | wrappers { 25 | preBuildCleanup() 26 | } 27 | 28 | steps { 29 | copyArtifacts(project_name_canonical) { 30 | buildSelector { 31 | upstreamBuild { 32 | fallbackToLastSuccessful false 33 | } 34 | } 35 | } 36 | shell 'mkdir -p build ; cd build ; unzip -o ../test-results.zip' 37 | shell './gradlew sonarqube -x test -Dsonar.host.url=$SONAR_HOST -Dsonar.scm.provider=git' 38 | } // steps 39 | } 40 | } // projectsWithTag.each 41 | } // use(common) 42 | 43 | -------------------------------------------------------------------------------- /common.groovy: -------------------------------------------------------------------------------- 1 | import javaposse.jobdsl.dsl.AbstractContext 2 | import javaposse.jobdsl.dsl.helpers.* 3 | 4 | class common { 5 | static { 6 | // Extend the scm{} DSL context with extra commands 7 | // https://github.com/jenkinsci/job-dsl-plugin/wiki/Extending-the-DSL 8 | ScmContext.metaClass.customGit { String url, String branch, Closure configure = null -> 9 | git { 10 | delegate.remote { 11 | delegate.url(url) 12 | delegate.credentials('jenkins-gitlab-id') 13 | } 14 | if (branch) { 15 | delegate.branch(branch) 16 | } 17 | if (configure) { 18 | delegate.configure(configure) 19 | } 20 | } 21 | } 22 | ScmContext.metaClass.customGit << { String url -> 23 | customGit(url, null) 24 | } 25 | } 26 | 27 | // Use the GitLab API to find a specific project based on group & name 28 | static Object gitlabProject( String groupName, String projectName, String token, String gitlabApiURL = 'https://gitlab.example.com/api/v3' ) { 29 | def groupsUrl = "${gitlabApiURL}/groups?search=${groupName}&private_token=${token}" 30 | def foundGroup = new URL(groupsUrl).openConnection().getContent().newReader() 31 | def group = new groovy.json.JsonSlurper().parse(foundGroup) 32 | if (group.id) { 33 | def projectsUrl = "${gitlabApiURL}/groups/${group.id[0]}/projects?search=${projectName}&private_token=${token}" 34 | def foundProject = new URL(projectsUrl).openConnection().getContent().newReader() 35 | def project = new groovy.json.JsonSlurper().parse(foundProject) 36 | return project 37 | } 38 | return [] 39 | } 40 | 41 | // 42 | // Use the GitLab API to query all projects that have a tag in their tag_list 43 | // 44 | static List projectsWithTag( String tagName, String token, String gitlabApiURL = 'https://git.example.com/api/v3' ) { 45 | def projectsUrl = "${gitlabApiURL}/projects/all?private_token=${token}&per_page=20" 46 | def projectSearch = new URL(projectsUrl) 47 | def connection = projectSearch.openConnection() 48 | 49 | def projectResults = new groovy.json.JsonSlurper().parse(connection.getContent().newReader()) 50 | 51 | def totalPages = connection.getHeaderFields()["X-Total-Pages"][0].toInteger() 52 | 53 | for (def page = 2; page <= totalPages; page++) { 54 | connection = new URL(projectsUrl+"&page=${page}").openConnection() 55 | def moreResults = new groovy.json.JsonSlurper().parse(connection.getContent().newReader()) 56 | projectResults += moreResults 57 | } 58 | 59 | return projectResults.findAll { project -> tagName in project.tag_list } 60 | } 61 | } 62 | --------------------------------------------------------------------------------