├── old ├── README.md ├── manually-provision-docker-node.groovy ├── get-stuck-label-provisioners.groovy └── fix-stuck-label-provisioners.groovy ├── user-contributed-examples ├── start-all-jobs-matching-name.groovy ├── README.md ├── cleanup-all-builds.groovy └── FindAndAbortJobsByNameAndClass.groovy ├── README.md ├── LICENSE.txt ├── clear-queue.groovy ├── set-tz-system-property-live.groovy ├── cleanup-offline-agents.groovy ├── list-versions-for-issue-report.groovy ├── find-running-pipeline-jobs.groovy ├── count-projects-by-type.groovy ├── get-ip-from-dns.groovy ├── find-all-failed-builds-for-project.groovy ├── delete-freestyle-jobs.groovy ├── reset-stale-lockable-resources.groovy ├── delete-disabled-users.groovy ├── disable-freestyle-jobs.groovy ├── count-active-jobs.groovy ├── aggressive-cloud-provisioning.groovy ├── delete-disabled-ldap-accounts.groovy ├── disable-all-update-sites.groovy ├── disable-slack-webhooks.groovy ├── jenkins-stats-csv.groovy ├── delete-all-agents-by-label.groovy ├── remove-all-user-favorites.groovy ├── find-all-freestyle-jobs-using-shell-command.groovy ├── set-folder-views-to-dropdown.groovy ├── generate-all-missing-jervis-jobs.groovy ├── delete-builds-pr-older-than-30days.groovy ├── delete-builds-branch-and-tag-older-than-120days.groovy ├── disable-jenkins-cli.groovy ├── list-all-root-projects-by-view.groovy ├── find-all-builds-within-24hr-containing-text.groovy ├── safe-restart.groovy ├── kill-all-day-old-builds.groovy ├── kill-all-builds-for-job.groovy ├── monitor-agent-queue.groovy ├── ec2-agents-provision-max-capacity.groovy ├── convert-scm-between-polling-webhooks.groovy ├── kill-all-builds-for-project.groovy ├── convert-ghprb-between-polling-webhooks.groovy ├── disable-jenkins-cli-permanently.groovy ├── jenkins-stats.groovy ├── find-all-projects-using-label.groovy ├── find-all-cron-schedules-by-frequency.groovy ├── notify-github-pr-comment.groovy ├── find-all-jobs-cloning-github-repository.groovy ├── export-jenkins-credentials.groovy ├── find-latest-git-tag-releases-from-pipeline-stage-name.groovy ├── generate-unix-jnlp-agent-script.groovy ├── generate-all-jervis-jobs.groovy ├── delete-multibranch-pipelines-associated-with-archived-repositories.groovy ├── cleanup-data-on-all-agents-threaded.groovy ├── delete-multibranch-pipeline-disabled-jobs.groovy └── audit-user-permissions.groovy /old/README.md: -------------------------------------------------------------------------------- 1 | # Old scripts 2 | 3 | These scripts are no longer relevant but still kept around for archival 4 | purposes. 5 | -------------------------------------------------------------------------------- /user-contributed-examples/start-all-jobs-matching-name.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | Author: https://github.com/deepy 3 | Warning: This code is unlicensed. 4 | See https://github.com/samrocketman/jenkins-script-console-scripts/blob/master/user-contributed-examples/README.md 5 | */ 6 | 7 | import hudson.model.Cause 8 | import hudson.model.Job 9 | import jenkins.model.Jenkins 10 | 11 | Jenkins.instance.getAllItems(AbstractItem.class).findAll { 12 | it.name.endsWith('-cleanup') 13 | }.each { 14 | it.scheduleBuild(0, new Cause.UserIdCause()) 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jenkins Script Console scripts 2 | 3 | This repository contains [Jenkins Script Console][sc] scripts which are useful 4 | for myself (Sam Gleske). 5 | 6 | Other helpful script console script repositories: 7 | 8 | - [CloudBees jenkins-scripts][cb]. 9 | - [JenkinsCI jenkins-scripts][js]. 10 | - [jenkins-bootstrap-shared contains script console scripts in `scripts/`][jbs] 11 | which is another project I created. 12 | - [sandscape contains script console scripts][ss] which is another project I 13 | created. 14 | 15 | [cb]: https://github.com/cloudbees/jenkins-scripts 16 | [jbs]: https://github.com/samrocketman/jenkins-bootstrap-shared 17 | [js]: https://github.com/jenkinsci/jenkins-scripts 18 | [sc]: https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+Script+Console 19 | [ss]: https://github.com/sandscape/sandscape 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2022 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /clear-queue.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /** 22 | Clears all builds out of the Jenkins queue. Useful for freeing up Jenkins 23 | work. 24 | */ 25 | import hudson.model.Queue 26 | Queue.instance.clear() 27 | -------------------------------------------------------------------------------- /set-tz-system-property-live.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2018 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | /* 23 | This script sets a system property in runtime (e.g. Timezone timestamps). 24 | https://wiki.jenkins-ci.org/display/JENKINS/Change+time+zone 25 | */ 26 | 27 | //https://wiki.jenkins-ci.org/display/JENKINS/Features+controlled+by+system+properties 28 | System.setProperty('org.apache.commons.jelly.tags.fmt.timeZone', 'America/New_York') 29 | -------------------------------------------------------------------------------- /cleanup-offline-agents.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script deletes all slaves from Jenkins which are offline. 23 | */ 24 | import jenkins.model.Jenkins 25 | 26 | println "Cleaning up offline slaves..." 27 | Jenkins.instance.slaves.each { 28 | if(it.getComputer().isOffline()) { 29 | println "Deleting ${it.name}" 30 | it.getComputer().doDoDelete() 31 | } 32 | } 33 | println "Done." 34 | -------------------------------------------------------------------------------- /list-versions-for-issue-report.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /** 22 | Print the version of Jenkins and all plugins. This is useful for filling out 23 | environment in Jenkins issues. 24 | */ 25 | 26 | import jenkins.model.Jenkins 27 | 28 | println "Jenkins version ${Jenkins.instance.version}" 29 | println Jenkins.instance.pluginManager.plugins.toList().sort { it.shortName }.collect { p -> 30 | "${p.shortName} ${p.version}" 31 | }.join('\n') 32 | -------------------------------------------------------------------------------- /find-running-pipeline-jobs.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script searches the Jenkins server runtime and finds all jobs which are 23 | running. Even those which do not plainly show up in the executor UI. 24 | */ 25 | 26 | import jenkins.model.Jenkins 27 | 28 | String findRunningPipelineJobs() { 29 | Jenkins.instance.getComputer('').getOneOffExecutors().collect { 30 | "${Jenkins.instance.rootUrl}${it.currentExecutable.url}" 31 | }.join('\n') 32 | } 33 | 34 | println findRunningPipelineJobs() 35 | -------------------------------------------------------------------------------- /old/manually-provision-docker-node.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2018 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Used to troubleshoot node successfully communicating with the Docker API to 23 | launch a node via docker plugin. 24 | */ 25 | import com.nirima.jenkins.plugins.docker.DockerCloud 26 | import com.nirima.jenkins.plugins.docker.DockerTemplate 27 | DockerCloud mycloud 28 | DockerTemplate mytemplate 29 | mycloud = Jenkins.instance.clouds.findAll { it.class == DockerCloud }[0] 30 | mytemplate = mycloud.templates[0] 31 | mycloud.provisionWithWait(mytemplate) 32 | -------------------------------------------------------------------------------- /count-projects-by-type.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script console script will count jobs by type. 23 | */ 24 | import hudson.model.Job 25 | import jenkins.model.Jenkins 26 | 27 | projects = [:] 28 | Jenkins.instance.getAllItems(Job.class).each { Job j -> 29 | String jc = j.class.simpleName 30 | if(!(jc in projects)) { 31 | projects[jc] = 0 32 | } 33 | projects[jc]++ 34 | } 35 | println "Count projects by type:" 36 | projects.each { k, v -> 37 | println " ${k}: ${v}" 38 | } 39 | null 40 | -------------------------------------------------------------------------------- /old/get-stuck-label-provisioners.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2018 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Find labels which are misreporting pending launches for clouds. This is 23 | only useful when there's no active cloud agents but there's builds in the queue 24 | waiting indefinitely. 25 | */ 26 | 27 | import hudson.model.Label 28 | import jenkins.model.Jenkins 29 | Jenkins.instance.labels.each { Label label -> 30 | if(label.nodeProvisioner.getPendingLaunches().size() > 0) { 31 | println "STUCK LABEL: ${label}" 32 | } 33 | } 34 | null 35 | -------------------------------------------------------------------------------- /get-ip-from-dns.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | DNS lookup hostname via Jenkins script console. 23 | 24 | This is for basically doing a DNS lookup of a hostname from the context of a 25 | Jenkins master. 26 | */ 27 | 28 | if(!binding.hasVariable('domain')) { 29 | domain = 'example.com' 30 | } 31 | 32 | if(!(domain in String)) { 33 | throw new Exception('PARAMETER ERROR: domain must be a string.') 34 | } 35 | 36 | InetAddress dnsInetAddress = InetAddress.getByName domain 37 | //print IP address 38 | println dnsInetAddress.hostAddress 39 | -------------------------------------------------------------------------------- /find-all-failed-builds-for-project.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /** 22 | Searches for all failed builds in a particular project. Useful for when the 23 | build history is not visible due to the amount of builds. 24 | */ 25 | import hudson.model.Result 26 | import hudson.model.ParametersAction 27 | 28 | Jenkins.instance.getItem('_jervis_generator').builds.findAll { 29 | //it.number > 715 && 30 | it.result == Result.FAILURE 31 | }.each { 32 | println "${it.getAction(ParametersAction).parameters.first().value}\n ${it.absoluteUrl}" 33 | } 34 | 35 | null 36 | -------------------------------------------------------------------------------- /delete-freestyle-jobs.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Delete all disabled FreeStyle and MatrixProject Jenkins jobs except for a 23 | FreeStyle job named "_jervis_generator". 24 | */ 25 | import hudson.matrix.MatrixProject 26 | import hudson.model.FreeStyleProject 27 | import hudson.model.Job 28 | import jenkins.model.Jenkins 29 | 30 | Jenkins.instance.getAllItems(Job.class).findAll { Job j -> 31 | ( j in FreeStyleProject || j in MatrixProject ) && 32 | j.isDisabled() && j.fullName != '_jervis_generator' 33 | }.each { Job j -> 34 | j.delete() 35 | println "Deleted ${j.fullName}" 36 | } 37 | 38 | null 39 | -------------------------------------------------------------------------------- /reset-stale-lockable-resources.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2021 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Sometimes builds do not release locks via the pipeline lock step. This 23 | script iterates all locks and forces them to be released if a build has a 24 | lock but is no longer running. 25 | */ 26 | import jenkins.model.Jenkins 27 | 28 | def lockManager = Jenkins.instance.getExtensionList('org.jenkins.plugins.lockableresources.LockableResourcesManager')[0] 29 | 30 | lockManager.resources.findAll { lock -> 31 | lock.isLocked() && !lock.build.isBuilding() 32 | }.each { lock -> 33 | println "Resetting stale lock ${lock}" 34 | lock.reset() 35 | } 36 | -------------------------------------------------------------------------------- /delete-disabled-users.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script is designed to iterate across all users in a Jenkins installation 23 | and automatically delete their account if they're disabled the authentication 24 | realm. 25 | */ 26 | 27 | import hudson.model.User 28 | import jenkins.model.Jenkins 29 | import org.acegisecurity.userdetails.UsernameNotFoundException 30 | 31 | User.all.each { u -> 32 | try { 33 | u.impersonate() 34 | } catch(UsernameNotFoundException e) { 35 | println "${u.id}: Deleting disabled account from ${Jenkins.instance.rootUrl}." 36 | u.delete() 37 | } 38 | } 39 | null 40 | -------------------------------------------------------------------------------- /disable-freestyle-jobs.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Disables all FreeStyle and MatrixProject Jenkins jobs except for a FreeStyle 23 | job named "_jervis_generator". 24 | */ 25 | import hudson.matrix.MatrixProject 26 | import hudson.model.FreeStyleProject 27 | import hudson.model.Job 28 | import jenkins.model.Jenkins 29 | 30 | Jenkins.instance.getAllItems(Job.class).findAll { Job j -> 31 | ( j in FreeStyleProject || j in MatrixProject ) && 32 | !j.isDisabled() && j.fullName != '_jervis_generator' 33 | }.each { Job j -> 34 | j.disabled = true 35 | j.save() 36 | println "Disabled ${j.fullName}" 37 | } 38 | 39 | null 40 | -------------------------------------------------------------------------------- /count-active-jobs.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script counts up all of the active builds across all agents and prints the total. 23 | */ 24 | import jenkins.model.Jenkins 25 | 26 | Jenkins j = Jenkins.instance 27 | 28 | int active_builds = 0 29 | int inactive_executors = 0 30 | j.slaves.each { slave -> 31 | if(!slave.computer.isOffline()) { 32 | def executors = slave.computer.executors 33 | executors.each { executor -> 34 | if(executor.isBusy()) { 35 | active_builds++ 36 | } else { 37 | inactive_executors++ 38 | } 39 | } 40 | } 41 | } 42 | println "Queue: ${j.queue.items.size()}, Active: ${active_builds}, Free executors: ${inactive_executors}" 43 | -------------------------------------------------------------------------------- /aggressive-cloud-provisioning.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Enable very aggressive cloud provisioning where over provisioning is not a 23 | concern (e.g. one shot executor clouds). 24 | 25 | Based on this write-up from CloudBees. 26 | https://support.cloudbees.com/hc/en-us/articles/115000060512-New-agents-are-not-being-provisioned-for-my-jobs-in-the-queue-when-I-have-agents-that-are-suspended 27 | */ 28 | 29 | System.setProperty('hudson.model.LoadStatistics.clock', '5000') 30 | System.setProperty('hudson.model.LoadStatistics.decay', '0.5') 31 | System.setProperty('hudson.agents.NodeProvisioner.MARGIN', '100') 32 | System.setProperty('hudson.agents.NodeProvisioner.MARGIN0', '1.0') 33 | 34 | println "Aggressive cloud provisioning configured." 35 | -------------------------------------------------------------------------------- /delete-disabled-ldap-accounts.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script is designed to iterate across all users in a Jenkins 23 | installation and automatically delete their account if they're disabled in 24 | LDAP. 25 | 26 | **Note:** Jenkins MUST be set up with a user search filter which determines 27 | via LDAP if an account is disabled. The following is an example. 28 | 29 | (&(uid={0})(!(loginShell=/bin/false))) 30 | */ 31 | 32 | import hudson.model.User 33 | import jenkins.model.Jenkins 34 | import org.acegisecurity.userdetails.UsernameNotFoundException 35 | 36 | User.all.each { u -> 37 | try { 38 | u.impersonate() 39 | } catch(UsernameNotFoundException e) { 40 | println "${u.id}: Deleting disabled account from ${Jenkins.instance.rootUrl}." 41 | u.delete() 42 | } 43 | } 44 | null 45 | -------------------------------------------------------------------------------- /disable-all-update-sites.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | /* 23 | This script completely disables all update sites in Jenkins, invalidates plugin 24 | upgrade data, and deletes the cached update data. 25 | 26 | Additionally, it sets a system property to disable all update sites. 27 | */ 28 | 29 | import hudson.model.UpdateSite 30 | import jenkins.model.Jenkins 31 | 32 | def j = Jenkins.instance 33 | for(UpdateSite site : j.getUpdateCenter().getSiteList()) { 34 | site.neverUpdate = true 35 | try { 36 | site.data = null 37 | site.dataLastReadFromFile = -1 38 | } catch(Exception e) {} 39 | site.dataTimestamp = 0 40 | new File(j.getRootDir(), "updates/${site.id}.json").delete() 41 | } 42 | 43 | //https://wiki.jenkins-ci.org/display/JENKINS/Features+controlled+by+system+properties 44 | System.setProperty('hudson.model.UpdateCenter.never', 'true') 45 | -------------------------------------------------------------------------------- /disable-slack-webhooks.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Disable Slack outbound webhooks. 23 | 24 | This patches for potentially unknown security vulnerabilities in the Jenkins 25 | slack plugin outbound webhooks. It makes sense to disable Slack outbound 26 | webhooks if your Jenkins instance is in a network in which slack.com can't 27 | reach. 28 | 29 | Copy script to $JENKINS_HOME/init.groovy.d/ 30 | 31 | Source: 32 | https://github.com/samrocketman/jenkins-script-console-scripts/blob/master/disable-slack-webhooks.groovy 33 | */ 34 | 35 | import jenkins.model.Jenkins 36 | import hudson.model.RootAction 37 | 38 | def j = Jenkins.instance; 39 | def removal = { lst -> 40 | lst.each { x -> 41 | if(x.getClass().name.contains("slack.webhook")) { 42 | //println "remove ${x}" 43 | lst.remove(x) 44 | } 45 | } 46 | } 47 | removal(j.getExtensionList(RootAction.class)) 48 | removal(j.actions) 49 | -------------------------------------------------------------------------------- /jenkins-stats-csv.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script generates reads freestyle job output produced by 23 | jenkins-stats.groovy and generates a CSV output. This is useful for loading 24 | into a calc program and generating graphs. 25 | */ 26 | List statsCsv = [] 27 | 28 | Jenkins.instance.getItem('__overall_metrics').builds.findAll { 29 | it.result == hudson.model.Result.SUCCESS 30 | }.each { 31 | it.logText.readAll().text.tokenize('\n').findAll { 32 | it.contains(':') && !it.contains('Finished:') && !it.contains('/') 33 | }.collect { 34 | it.tokenize(':')*.trim() 35 | }.with { 36 | //println it 37 | if(!statsCsv) { 38 | statsCsv << it*.get(0).join(',') 39 | } 40 | statsCsv << it*.get(1).join(',') 41 | } 42 | } 43 | String header = statsCsv[0] 44 | // print a CSV of all data to-date. 45 | println((([header] + statsCsv[1..-1].reverse()).findAll { it }).join('\n')) 46 | -------------------------------------------------------------------------------- /delete-all-agents-by-label.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Delete all Jenkins agents which are associated with a particular label. 23 | 24 | WARNING this script will permanently delete agents including all settings 25 | associated with an agent. If the agent is associated with a Jenkins Cloud 26 | then INFRASTRUCTURE WILL ALSO LIKELY BE DELETED. 27 | */ 28 | 29 | import hudson.model.Label 30 | import hudson.model.labels.LabelAtom 31 | import jenkins.model.Jenkins 32 | 33 | // delete all agents associated with label 34 | String agent_label = 'some-agent' 35 | 36 | Label agent = (Jenkins.instance.getLabel(agent_label)) ?: (new LabelAtom(agent_label)) 37 | 38 | List computers = agent.nodes*.computer 39 | 40 | // put all agents in offline mode (some may be in use) 41 | computers*.doToggleOffline() 42 | 43 | // delete inactive agents 44 | computers.findAll { 45 | it.countBusy() == 0 46 | }.with { 47 | it*.doDoDelete() 48 | println "${it.size()} agents deleted." 49 | } 50 | -------------------------------------------------------------------------------- /remove-all-user-favorites.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Automatically remove auto-favorites for every user and disable favorites 23 | going forward for affected users. 24 | 25 | Since new users get created on a regular basis during onboarding this script 26 | could run periodically to clean up newly onboarded users. 27 | */ 28 | 29 | import hudson.model.User 30 | import io.jenkins.blueocean.autofavorite.user.FavoritingUserProperty 31 | import hudson.plugins.favorite.user.FavoriteUserProperty 32 | 33 | User.all.each { user -> 34 | if(!user?.getProperty(FavoritingUserProperty)?.autofavoriteEnabled) { 35 | return 36 | } 37 | user.getProperty(FavoritingUserProperty).with { autoFavorite -> 38 | autoFavorite.autofavoriteEnabled = false 39 | } 40 | user.getProperty(FavoriteUserProperty).with { favorites -> 41 | favorites.allFavorites.each { String cursed -> 42 | favorites.removeFavorite(cursed) 43 | } 44 | } 45 | println "Disabled auto-favoriting for ${user}." 46 | println "Removed all favorites from ${user}." 47 | user.save() 48 | } 49 | null 50 | -------------------------------------------------------------------------------- /user-contributed-examples/README.md: -------------------------------------------------------------------------------- 1 | # User Contributed Examples 2 | 3 | For a while now, I've been trying to figure out how to nicely accept people's 4 | examples while at the same time keeping my repository slim for me. The original 5 | intent of this repository is for my day to day Jenkins operations and 6 | consulting. However, I don't want user-contributed examples to go to waste for 7 | those who want it. 8 | 9 | # Contributing scripts 10 | 11 | **Please only contribute scripts in which you wrote yourself**. It's fine if 12 | your script is based on my script or others as long as the script you made is 13 | your own. 14 | 15 | # I may make changes 16 | 17 | Since I consider scripts contributed as an educational exercise (for myself and 18 | others), I may make changes to scripts in order to change their code style or to 19 | present the same logic in a more groovy-native way. The purpose of me making 20 | this changes is to help the author learn groovy better. 21 | 22 | # Untested warning 23 | 24 | None of these scripts have been tested by me. Even if I make modifications I 25 | don't generally test them. These are purely educational examples and may not 26 | work out of the box. I may make fixes myself if there are obvious bugs or I 27 | will accept contributions for licensed scripts. 28 | 29 | # License warning 30 | 31 | Source code in this directory **does not** fall under my personal [MIT 32 | License](../LICENSE.txt). 33 | 34 | If a script does not have a license header, then I will only accept 35 | contributions to said script from the original author. 36 | 37 | None of this code comes with any kind of license or warranty unless the 38 | contributor provided it. I am merely accepting code from excited contributors 39 | who would like to add to the knowledge base of these scripts. 40 | 41 | # Scripts with missing licenses 42 | 43 | If you would like to add your own license to your script so that others are free 44 | to copy/modify your code, then feel free to open a pull request to your own 45 | script. 46 | 47 | > Please note: I will only accept pull requests for license changes from the 48 | > original author of the contributed script. I define the original author as 49 | > someone who first opened the pull request and I assume they have the rights to 50 | > license their own code. 51 | -------------------------------------------------------------------------------- /user-contributed-examples/cleanup-all-builds.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Marco Davalos https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | /* 23 | This script deletes all builds from all job of the Jenkins instance keeping the 24 | last ${MAX_BUILDS} builds. It checks the build to delete is not building at that 25 | moment and it also keeps the builds that are: 26 | 27 | - keepLog (marked as "Keep this build forever") 28 | - lastStableBuild 29 | - lastSuccessfulBuild 30 | - lastSuccessfulBuild 31 | - lastUnstableBuild 32 | - lastUnsuccessfulBuild 33 | */ 34 | 35 | import jenkins.model.Jenkins 36 | import hudson.model.Job 37 | 38 | int MAX_BUILDS = 5 // max builds to keep 39 | 40 | Jenkins.instance.getAllItems(Job.class).each { job -> 41 | 42 | job.builds.drop(MAX_BUILDS).findAll { 43 | 44 | !it.keepLog && 45 | !it.building && 46 | it != job.lastStableBuild && 47 | it != job.lastSuccessfulBuild && 48 | it != job.lastUnstableBuild && 49 | it != job.lastUnsuccessfulBuild 50 | 51 | }.each { build -> 52 | build.delete() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /find-all-freestyle-jobs-using-shell-command.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /** 22 | Search all freestyle jobs in a Jenkins instance. Find any job containing any 23 | one of the commands being used in a list of strings. 24 | */ 25 | import hudson.model.FreeStyleProject 26 | import hudson.tasks.Shell 27 | import jenkins.model.Jenkins 28 | 29 | //find any of the following strings existing in a freestyle job in Jenkins 30 | List strings = ['git clone', 'echo'] 31 | //require a freestyle job to contain all of the strings in the list within shell steps? 32 | Boolean containsAll = false 33 | 34 | println Jenkins.instance.getAllItems(FreeStyleProject.class).findAll { job -> 35 | job.builders.findAll { it in Shell } && 36 | job.builders.findAll { 37 | it in Shell 38 | }.collect { shell -> 39 | shell.command 40 | }.join('\n').with { String script -> 41 | Boolean found = ((!containsAll) in strings.collect { script.contains(it) }) 42 | //XOR the found result 43 | found ^ containsAll 44 | } 45 | }.collect { job -> 46 | job.absoluteUrl 47 | }.join('\n') 48 | -------------------------------------------------------------------------------- /old/fix-stuck-label-provisioners.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2018 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Fix labels which are misreporting pending launches for clouds. This occurs 23 | when a build gets stuck in the queue and there's no apparent reason it 24 | should be. To debug this one can create a new logger for the following 25 | classes: 26 | 27 | com.github.kostyasha.yad.DockerProvisioningStrategy 28 | hudson.model.LoadStatistics$LoadStatisticsUpdater 29 | hudson.slaves.NodeProvisioner 30 | 31 | */ 32 | 33 | import hudson.model.Label 34 | import hudson.slaves.NodeProvisioner.PlannedNode 35 | import jenkins.model.Jenkins 36 | 37 | Jenkins.instance.labels.each { Label label -> 38 | try { 39 | if(label.nodeProvisioner.getPendingLaunches().size() > 0) { 40 | println "STUCK LABEL: ${label}" 41 | label.@nodeProvisioner.@pendingLaunches.set([] as List) 42 | println "FIXED" 43 | } 44 | } catch(Exception e) { 45 | //label.@nodeProvisioner.@pendingLaunches.set([] as List) 46 | println "ERROR: ${label}" 47 | } 48 | } 49 | null 50 | -------------------------------------------------------------------------------- /set-folder-views-to-dropdown.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2018 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | /* 23 | Search a Jenkins instance for all folders which have a large number of views 24 | and change it to a dropdown list. 25 | */ 26 | 27 | import com.cloudbees.hudson.plugins.folder.Folder 28 | import hudson.views.tabbar.DropDownViewsTabBar 29 | 30 | //maximum number of views before converting views tab to a dropdown list 31 | if(!binding.hasVariable('max_views')) { 32 | max_views = 5 33 | } 34 | 35 | if(max_views in String) { 36 | max_views = Integer.decode(max_views) 37 | } 38 | 39 | //type check user defined parameters/bindings 40 | if(!(max_views in Integer)) { 41 | throw new Exception('PARAMETER ERROR: max_views must be an integer.') 42 | } 43 | 44 | List message = [] 45 | Jenkins.instance.getAllItems(Folder.class).each { i -> 46 | if(i.views.size() > max_views && !(i.folderViews.tabBar in DropDownViewsTabBar)) { 47 | i.folderViews.tabBar = new DropDownViewsTabBar() 48 | i.save() 49 | message << i.fullName.toString() 50 | } 51 | } 52 | 53 | if(message.size() > 0) { 54 | println 'Projects with views tab changed to dropdown:' 55 | println " ${message.join('\n ')}" 56 | } 57 | -------------------------------------------------------------------------------- /generate-all-missing-jervis-jobs.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | /* 23 | Finds all Jervis projects which do not use the Jervis Filter trait and 24 | regenerates the jobs from Job DSL scripts. Useful for partially completed 25 | code migrations. 26 | */ 27 | import hudson.model.ParametersAction 28 | import hudson.model.StringParameterValue 29 | import net.gleske.scmfilter.impl.trait.JervisFilterTrait 30 | import org.jenkinsci.plugins.github_branch_source.GitHubSCMSource 31 | import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject 32 | 33 | def generator_job = Jenkins.instance.getItemByFullName('_jervis_generator') 34 | 35 | Jenkins.instance.getAllItems(WorkflowMultiBranchProject).findAll { project -> 36 | null == project.sources[0].source.traits.find { filter -> 37 | filter in JervisFilterTrait 38 | } 39 | }.collect { project -> 40 | project.getSCMSources().find { source -> 41 | source in GitHubSCMSource 42 | }.getRepositoryUrl() -~ '^https://github.com/' 43 | }.sort().unique().each { String project -> 44 | def actions = new ParametersAction([new StringParameterValue('project', project)]) 45 | generator_job.scheduleBuild2(0, actions) 46 | println "Scheduled project ${project}." 47 | } 48 | 49 | null 50 | -------------------------------------------------------------------------------- /delete-builds-pr-older-than-30days.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Delete pr builds older than 30 days on a Jenkins instance using multibranch 23 | pipelines inside of folders. 24 | */ 25 | import com.cloudbees.hudson.plugins.folder.Folder 26 | import java.util.Date 27 | import jenkins.model.Jenkins 28 | import org.jenkinsci.plugins.workflow.job.WorkflowJob 29 | import org.jenkinsci.plugins.workflow.job.WorkflowRun 30 | import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject 31 | 32 | long DAY_IN_MS = 1000 * 60 * 60 * 24 33 | int days = 30 34 | Date thirtyDaysAgo = new Date(System.currentTimeMillis() - (days * DAY_IN_MS)) 35 | 36 | 37 | Jenkins.instance.getAllItems(WorkflowJob).findAll { 38 | it?.parent in WorkflowMultiBranchProject && 39 | it?.parent?.parent in Folder 40 | }.findAll { WorkflowJob job -> 41 | job.name.startsWith('PR-') 42 | }.each { WorkflowJob job -> 43 | job?.builds.each { WorkflowRun build -> 44 | if((new Date(build.startTimeInMillis)).before(thirtyDaysAgo)) { 45 | println "Deleting ${days} day ${build}" 46 | build.delete() 47 | } 48 | } 49 | if(!job?.builds) { 50 | println "Delete empty ${job}" 51 | job.delete() 52 | } 53 | } 54 | 55 | null 56 | -------------------------------------------------------------------------------- /delete-builds-branch-and-tag-older-than-120days.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Delete branch and tag builds older than 120 days on a Jenkins instance using 23 | multibranch pipelines inside of folders. 24 | */ 25 | import com.cloudbees.hudson.plugins.folder.Folder 26 | import java.util.Date 27 | import jenkins.model.Jenkins 28 | import org.jenkinsci.plugins.workflow.job.WorkflowJob 29 | import org.jenkinsci.plugins.workflow.job.WorkflowRun 30 | import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject 31 | 32 | long DAY_IN_MS = 1000 * 60 * 60 * 24 33 | int days = 120 34 | Date fourMonthsAgo = new Date(System.currentTimeMillis() - (days * DAY_IN_MS)) 35 | 36 | 37 | Jenkins.instance.getAllItems(WorkflowJob).findAll { 38 | it?.parent in WorkflowMultiBranchProject && 39 | it?.parent?.parent in Folder 40 | }.findAll { WorkflowJob job -> 41 | !job.name.startsWith('PR-') 42 | }.each { WorkflowJob job -> 43 | job?.builds?.each { WorkflowRun build -> 44 | if((new Date(build.startTimeInMillis)).before(fourMonthsAgo)) { 45 | println "Deleting ${days} day ${build}" 46 | build.delete() 47 | } 48 | } 49 | if(!job?.builds) { 50 | println "Delete empty ${job}" 51 | job.delete() 52 | } 53 | } 54 | 55 | null 56 | -------------------------------------------------------------------------------- /disable-jenkins-cli.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2015, Kohsuke Kawaguchi 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | /* 25 | Disable Jenkins CLI. 26 | 27 | Copy script to $JENKINS_HOME/init.groovy.d/ 28 | 29 | This init script for Jenkins fixes a zero day vulnerability. 30 | http://jenkins-ci.org/content/mitigating-unauthenticated-remote-code-execution-0-day-jenkins-cli 31 | https://github.com/jenkinsci-cert/SECURITY-218 32 | https://github.com/samrocketman/jenkins-script-console-scripts/blob/master/disable-jenkins-cli.groovy 33 | */ 34 | 35 | import jenkins.AgentProtocol 36 | import jenkins.model.Jenkins 37 | import hudson.model.RootAction 38 | 39 | //determined if changes were made 40 | configChanged = false 41 | 42 | // disabled CLI access over TCP listener (separate port) 43 | def p = AgentProtocol.all() 44 | p.each { x -> 45 | if(x.name && x.name.contains("CLI")) { 46 | //println "remove ${x}" 47 | p.remove(x) 48 | configChanged = true 49 | } 50 | } 51 | 52 | // disable CLI access over /cli URL 53 | def removal = { lst -> 54 | lst.each { x -> 55 | if(x.getClass().name.contains("CLIAction")) { 56 | //println "remove ${x}" 57 | lst.remove(x) 58 | configChanged = true 59 | } 60 | } 61 | } 62 | def j = Jenkins.instance; 63 | removal(j.getExtensionList(RootAction.class)) 64 | removal(j.actions) 65 | 66 | if(configChanged) { 67 | println 'Jenkins CLI has been disabled.' 68 | } else { 69 | println 'Nothing changed. Jenkins CLI already disabled.' 70 | } 71 | -------------------------------------------------------------------------------- /list-all-root-projects-by-view.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | /* 23 | List all root projects by view and discover projects which are not 24 | associated with a view. 25 | */ 26 | 27 | import hudson.model.Job 28 | import hudson.plugins.git.GitSCM 29 | import hudson.scm.NullSCM 30 | import hudson.scm.SCM 31 | import jenkins.model.Jenkins 32 | import org.jenkinsci.plugins.multiplescms.MultiSCM 33 | 34 | void printSCM(Job j, SCM scm) { 35 | switch(scm) { 36 | case NullSCM: 37 | println " ${j.displayName} has no SCM configured." 38 | break 39 | case GitSCM: 40 | println " ${j.displayName} -> ${scm.repositories[0].URIs[0]}" 41 | break 42 | case MultiSCM: 43 | scm.configuredSCMs.each { s -> 44 | printSCM(j, s) 45 | } 46 | break 47 | default: 48 | throw new Exception("SCM class not supported: ${scm.class}") 49 | break 50 | } 51 | } 52 | 53 | j = Jenkins.instance 54 | 55 | discovered_jobs = [].toSet() 56 | 57 | Jenkins.instance.views.findAll { v -> 58 | v.displayName != j.primaryView.displayName 59 | }.each { v -> 60 | println v.displayName 61 | v.items.each { j -> 62 | discovered_jobs << j 63 | printSCM(j, j.scm) 64 | } 65 | } 66 | 67 | println "Jobs under primary view '${j.primaryView.displayName}' not covered by other views" 68 | ((j.primaryView.items as Set) - discovered_jobs).each { j -> 69 | printSCM(j, j.scm) 70 | } 71 | 72 | null 73 | -------------------------------------------------------------------------------- /find-all-builds-within-24hr-containing-text.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Find all builds performed within that past 24 hours whose console output 23 | contains the text listed in the search binding. 24 | */ 25 | import hudson.console.PlainTextConsoleOutputStream 26 | import java.io.ByteArrayOutputStream 27 | import java.util.Date 28 | import jenkins.model.Jenkins 29 | import org.apache.commons.io.IOUtils 30 | import org.jenkinsci.plugins.workflow.job.WorkflowJob 31 | import org.jenkinsci.plugins.workflow.job.WorkflowRun 32 | 33 | if(binding.hasVariable('out') && binding.hasVariable('build')) { 34 | search = build.buildVariableResolver.resolve('search') 35 | } 36 | int HOURS_IN_MS = 3600000 37 | Date oneDayAgo = new Date(System.currentTimeMillis() - (24 * HOURS_IN_MS)) 38 | 39 | println Jenkins.instance.getAllItems(WorkflowJob).findAll { 40 | WorkflowRun build = it.builds.first() 41 | (new Date(build.startTimeInMillis)).after(oneDayAgo) 42 | }.collect { 43 | it.builds.findAll { WorkflowRun build -> 44 | (new Date(build.startTimeInMillis)).after(oneDayAgo) 45 | } 46 | }.flatten().findAll { WorkflowRun build -> 47 | ByteArrayOutputStream os = new ByteArrayOutputStream() 48 | InputStreamReader is = build.logText.readAll() 49 | PlainTextConsoleOutputStream pos = new PlainTextConsoleOutputStream(os) 50 | IOUtils.copy(is, pos) 51 | String text = os.toString() 52 | Boolean result = text.contains(search) 53 | os.close() 54 | is.close() 55 | pos.close() 56 | result 57 | }*.absoluteUrl.join('\n') 58 | -------------------------------------------------------------------------------- /safe-restart.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2018 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script starts a background thread which will wait for Jenkins to finish 23 | executing jobs before restarting. The thread will abort if shutdown mode is 24 | disabled before jobs finish. 25 | 26 | Tested on Jenkins ver. 2.7.1 27 | */ 28 | 29 | import hudson.model.RestartListener 30 | import java.util.logging.Level 31 | import java.util.logging.Logger 32 | 33 | //user configurable variable 34 | if(!binding.hasVariable('timeout_seconds')) { 35 | timeout_seconds = 60 36 | } 37 | 38 | if(timeout_seconds in String) { 39 | timeout_seconds = Integer.decode(timeout_seconds) 40 | } 41 | 42 | //type check user defined parameters/bindings 43 | if(!(timeout_seconds in Integer)) { 44 | throw new Exception('PARAMETER ERROR: timeout_seconds must be an integer.') 45 | } 46 | 47 | Logger logger = Logger.getLogger('jenkins.instance.restart') 48 | 49 | //start a background thread 50 | def thread = Thread.start { 51 | logger.log(Level.INFO, "Jenkins safe restart initiated.") 52 | while(true) { 53 | if(Jenkins.instance.isQuietingDown()) { 54 | if(RestartListener.isAllReady()) { 55 | Jenkins.instance.restart() 56 | } 57 | logger.log(Level.INFO, "Jenkins jobs are not idle. Waiting ${timeout_seconds} seconds before next restart attempt.") 58 | sleep(timeout_seconds*1000) 59 | } 60 | else { 61 | logger.log(Level.INFO, "Shutdown mode not enabled. Jenkins restart aborted.") 62 | break 63 | } 64 | } 65 | } 66 | 67 | println 'A safe restart has been scheduled. See the Jenkins logs for restart status updates. Logger is jenkins.instance.restart.' 68 | -------------------------------------------------------------------------------- /kill-all-day-old-builds.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | /* 23 | This script kills all builds which have been running longer than one day. 24 | This helps to keep a Jenkins instance healthy and executors free from 25 | existing stale build runs. 26 | 27 | Supported types to kill: 28 | FreeStyleBuild 29 | WorkflowRun (Jenkins Pipelines) 30 | */ 31 | 32 | import hudson.model.FreeStyleBuild 33 | import hudson.model.Job 34 | import hudson.model.Result 35 | import hudson.model.Run 36 | import java.util.Calendar 37 | import jenkins.model.Jenkins 38 | import org.jenkinsci.plugins.workflow.job.WorkflowRun 39 | 40 | //24 hours in a day, 3600 seconds in 1 hour, 1000 milliseconds in 1 second 41 | long time_in_millis = 24*3600*1000 42 | Calendar rightNow = Calendar.getInstance() 43 | 44 | Jenkins.instance.getAllItems(Job.class).collect { Job job -> 45 | //find all matching items and return a list but if null then return an empty list 46 | job.builds.findAll { Run run -> 47 | run.isBuilding() && ((rightNow.getTimeInMillis() - run.getStartTimeInMillis()) > time_in_millis) 48 | } ?: [] 49 | }.flatten().each { Run item -> 50 | if(item in WorkflowRun) { 51 | WorkflowRun run = (WorkflowRun) item 52 | //hard kill 53 | run.doKill() 54 | println "Killed ${run}" 55 | } else if(item in FreeStyleBuild) { 56 | FreeStyleBuild run = (FreeStyleBuild) item 57 | run.executor.interrupt(Result.ABORTED) 58 | Thread.sleep(1000) 59 | run.executor.stop() 60 | println "Killed ${run}" 61 | } else { 62 | println "WARNING: Don't know how to handle ${item.class}" 63 | } 64 | } 65 | 66 | //null means there will be no return result for the script 67 | null 68 | -------------------------------------------------------------------------------- /kill-all-builds-for-job.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script will kill all in-progress builds associated with a Jenkins job. 23 | */ 24 | import hudson.model.FreeStyleBuild 25 | import hudson.model.Result 26 | import hudson.model.Run 27 | import jenkins.model.Jenkins 28 | import org.jenkinsci.plugins.workflow.job.WorkflowRun 29 | import org.jenkinsci.plugins.workflow.support.steps.StageStepExecution 30 | 31 | if(!binding.hasVariable('dryRun')) { 32 | dryRun = true 33 | } 34 | if(!binding.hasVariable('projectFullName')) { 35 | projectFullName = 'folder/project' 36 | } 37 | 38 | if(dryRun in String) { 39 | dryRun = (dryRun.toLowerCase() != 'false') as Boolean 40 | } 41 | 42 | //type check user defined parameters/bindings 43 | if(!(dryRun in Boolean)) { 44 | throw new Exception('PARAMETER ERROR: dryRun must be a boolean.') 45 | } 46 | if(!(projectFullName in String)) { 47 | throw new Exception('PARAMETER ERROR: projectFullName must be a string.') 48 | } 49 | 50 | Jenkins.instance.getItemByFullName(projectFullName).builds.each { Run item -> 51 | if(item.isBuilding()) { 52 | if(item in WorkflowRun) { 53 | WorkflowRun run = (WorkflowRun) item 54 | if(!dryRun) { 55 | //hard kill 56 | run.doKill() 57 | //release pipeline concurrency locks 58 | StageStepExecution.exit(run) 59 | } 60 | println "Killed ${run}" 61 | } else if(item in FreeStyleBuild) { 62 | FreeStyleBuild run = (FreeStyleBuild) item 63 | if(!dryRun) { 64 | run.executor.interrupt(Result.ABORTED) 65 | } 66 | println "Killed ${run}" 67 | } else { 68 | println "WARNING: Don't know how to handle ${item.class}" 69 | } 70 | } 71 | } 72 | 73 | //null so no result shows up 74 | null 75 | -------------------------------------------------------------------------------- /monitor-agent-queue.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This Jenkins script console script will monitor Jenkins agents and the build 23 | queue. It will notify slack when agents are offline and when jobs are stuck 24 | in the build queue for longer than the specified time threshold (2 hours). 25 | */ 26 | 27 | import hudson.model.Queue 28 | import jenkins.model.Jenkins 29 | import jenkins.plugins.slack.SlackNotifier 30 | import jenkins.plugins.slack.StandardSlackService 31 | 32 | //two hours in milliseconds; the value must be in milliseconds 33 | long queueTimeAlertThreshold = 7200000 34 | 35 | List offlineAgents = Jenkins.instance.slaves.findAll { 36 | it.computer.isOffline() && ! it.computer.isTemporarilyOffline() 37 | } 38 | 39 | List message = ['Jenkins agents and build queue monitor service:'] 40 | 41 | if(offlineAgents) { 42 | println("Offline agents:\n ${offlineAgents*.name.join('\n ')}") 43 | message << "Number of agents offline: ${offlineAgents.size()}" 44 | } 45 | 46 | List longQueuedJobs = Queue.instance.buildableItems.findAll { 47 | System.currentTimeMillis() - it.inQueueSince > queueTimeAlertThreshold 48 | } 49 | 50 | if(longQueuedJobs) { 51 | message << "There are ${longQueuedJobs.size()} jobs waiting in the build queue for longer than ${queueTimeAlertThreshold} milliseconds." 52 | } 53 | 54 | if(message.size() > 1) { 55 | SlackNotifier.DescriptorImpl notifier = Jenkins.instance.getExtensionList('jenkins.plugins.slack.SlackNotifier$DescriptorImpl')[0] 56 | StandardSlackService publisher = new StandardSlackService( 57 | notifier.baseUrl, 58 | notifier.teamDomain, 59 | '', 60 | notifier.tokenCredentialId, 61 | notifier.botUser, 62 | notifier.room) 63 | message << "(<${build.getAbsoluteUrl()}console|Open monitoring job>)" 64 | publisher.publish(message.join('\n'), 'danger') 65 | println "Slack send message:\n ${message.join('\n ')}" 66 | } 67 | else { 68 | println 'All Green, trap is clean.' 69 | } 70 | -------------------------------------------------------------------------------- /ec2-agents-provision-max-capacity.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | When given an agent label associated with an EC2 plugin template, this 23 | script will provision the EC2 template to max capacity. 24 | 25 | WARNING this script was tested when provisioning limits were defined for the 26 | EC2 plugin with only 1 template. Multiple templates and labels were not 27 | tested. This was also not tested without EC2 limits set. This was tested 28 | in a very simple environment where agent labels for EC2 instances did not 29 | conflict with any other Jenkins agent labels. 30 | 31 | WARNING THIS SCRIPT MAY COST YOU REAL MONEY BECAUSE YOUR AWS CAPACITY MAY 32 | NOT BE FREE. I AM NOT RESPONSIBLE AND YOU HAVE BEEN WARNED. 33 | 34 | ec2 plugin 1.41 35 | */ 36 | 37 | import hudson.model.Label 38 | import hudson.model.Node 39 | import hudson.model.labels.LabelAtom 40 | import hudson.plugins.ec2.AmazonEC2Cloud 41 | import hudson.plugins.ec2.EC2AbstractSlave 42 | import hudson.plugins.ec2.SlaveTemplate 43 | import jenkins.model.Jenkins 44 | 45 | // provision max EC2 instances for agent label 46 | String agent_label = 'aws-agent' 47 | 48 | Label agent = (Jenkins.instance.getLabel(agent_label)) ?: (new LabelAtom(agent_label)) 49 | Jenkins.instance.clouds.findAll { cloud -> 50 | int current_agents = agent.nodes.findAll { node -> 51 | node in EC2AbstractSlave && 52 | node.cloud.is(cloud) 53 | }.size() 54 | 55 | (cloud in AmazonEC2Cloud) && 56 | cloud.canProvision(agent) && 57 | (cloud.getTemplate(agent).instanceCap - current_agents) > 0 58 | }.each { AmazonEC2Cloud cloud -> 59 | SlaveTemplate t = cloud.getTemplate(agent) 60 | int desired_agents = t.instanceCap - agent.nodes.findAll { 61 | it in EC2AbstractSlave && 62 | it.cloud.is(cloud) 63 | }.size() 64 | List nodes = t.provision(desired_agents, EnumSet.of(SlaveTemplate.ProvisionOptions.FORCE_CREATE)) 65 | nodes.each { node -> 66 | Jenkins.instance.addNode(node) 67 | } 68 | println "Provisioned ${nodes.size()} new agents." 69 | } ?: println('No agents provisioned.') 70 | 71 | null 72 | -------------------------------------------------------------------------------- /convert-scm-between-polling-webhooks.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script iterates through all Jenkins jobs and finds only jobs that have 23 | SCM polling or GitHub webhooks triggers configured. In each project, it 24 | will do the following: 25 | 26 | - Stop and remove the old trigger. 27 | - Create a new trigger depending on whether webhooks or polling will be 28 | used. 29 | - Start the trigger. If GitHubPushTrigger, then will also register hook 30 | with GitHub. 31 | 32 | Note: This script requires the GitHub plugin to be configured with GitHub 33 | API token which has the scope admin:hook on projects. 34 | */ 35 | import com.cloudbees.jenkins.GitHubPushTrigger 36 | import hudson.model.Job 37 | import hudson.triggers.SCMTrigger 38 | import hudson.triggers.Trigger 39 | 40 | // user configurable settings 41 | enableWebhooks = true 42 | pollSpec = 'H/5 * * * *' 43 | dryRun = true 44 | 45 | List affected_jobs = Jenkins.instance.getAllItems(Job.class).findAll { Job job 46 | job.getTrigger((enableWebhooks) ? SCMTrigger.class : GitHubPushTrigger.class ) 47 | }.sort { it.fullName } 48 | 49 | if(dryRun) { 50 | println([ 51 | '[DRYRUN] would convert to using ', 52 | (enableWebhooks)? 'GitHub webhooks': 'SCM polling', 53 | ':\n ', 54 | ((affected_jobs.collect { it.displayName }) ?: ['No jobs would change.']).join('\n ') 55 | ].join()) 56 | } 57 | 58 | ((dryRun)? [] : affected_jobs).each { Job job -> 59 | Trigger trigger = job.getTrigger((enableWebhooks) ? SCMTrigger.class : GitHubPushTrigger.class) 60 | //stop and remove old trigger 61 | trigger.stop() 62 | job.removeTrigger(trigger.descriptor) 63 | //create a new trigger 64 | trigger = (enableWebhooks) ? new GitHubPushTrigger() : new SCMTrigger(pollSpec) 65 | job.addTrigger(trigger) 66 | //start the trigger (if GitHubPushTrigger, then webhooks are registered with GitHub project) 67 | trigger.start(job, true) 68 | println "Converted ${job.displayName} to use ${(enableWebhooks)? 'GitHub webhooks': 'SCM polling'}." 69 | } 70 | //discard script console result output 71 | null 72 | -------------------------------------------------------------------------------- /kill-all-builds-for-project.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script will kill all in-progress builds associated with a project that 23 | may have one or more jobs. 24 | */ 25 | 26 | import hudson.model.FreeStyleBuild 27 | import hudson.model.Job 28 | import hudson.model.Result 29 | import hudson.model.Run 30 | import jenkins.model.Jenkins 31 | import org.jenkinsci.plugins.workflow.job.WorkflowRun 32 | import org.jenkinsci.plugins.workflow.support.steps.StageStepExecution 33 | 34 | if(!binding.hasVariable('dryRun')) { 35 | dryRun = true 36 | } 37 | if(!binding.hasVariable('projectFullNameStartsWith')) { 38 | projectFullNameStartsWith = 'folder/project' 39 | } 40 | 41 | if(dryRun in String) { 42 | dryRun = (dryRun.toLowerCase() != 'false') as Boolean 43 | } 44 | 45 | //type check user defined parameters/bindings 46 | if(!(dryRun in Boolean)) { 47 | throw new Exception('PARAMETER ERROR: dryRun must be a boolean.') 48 | } 49 | if(!(projectFullNameStartsWith in String)) { 50 | throw new Exception('PARAMETER ERROR: projectFullNameStartsWith must be a string.') 51 | } 52 | 53 | Jenkins.instance.getAllItems(Job.class).findAll { 54 | it.fullName.startsWith(projectFullNameStartsWith) && it.isBuilding() 55 | }.collect { Job job -> 56 | //find all matching items and return a list but if null then return an empty list 57 | job.builds.findAll { Run run -> 58 | run.isBuilding() 59 | } ?: [] 60 | }.sum().each { Run item -> 61 | if(item in WorkflowRun) { 62 | WorkflowRun run = (WorkflowRun) item 63 | if(!dryRun) { 64 | //hard kill 65 | run.doKill() 66 | //release pipeline concurrency locks 67 | StageStepExecution.exit(run) 68 | } 69 | println "Killed ${run}" 70 | } else if(item in FreeStyleBuild) { 71 | FreeStyleBuild run = (FreeStyleBuild) item 72 | if(!dryRun) { 73 | run.executor.interrupt(Result.ABORTED) 74 | } 75 | println "Killed ${run}" 76 | } else { 77 | println "WARNING: Don't know how to handle ${item.class}" 78 | } 79 | } 80 | 81 | //null so no result shows up 82 | null 83 | -------------------------------------------------------------------------------- /convert-ghprb-between-polling-webhooks.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script iterates through all Jenkins jobs and finds only jobs that have 23 | GHPRB triggers configured. In each GHPRB configured job this script does 24 | the following: 25 | 26 | - Disables cron polling. (or enables it) 27 | - Enables managing webhooks for the repository. (or disables it) 28 | - Saves the job configuration. 29 | - Starts the trigger so webhooks are active in Jenkins and registered with 30 | the GitHub project via GitHub API. 31 | */ 32 | 33 | import hudson.model.Job 34 | import hudson.triggers.Trigger 35 | import java.lang.reflect.Field 36 | import org.jenkinsci.plugins.ghprb.GhprbTrigger 37 | 38 | //true - convert from polling to webhooks 39 | //false - convert from webhooks to polling 40 | pollToWebhooks = true 41 | cron_field = 'H/15 * * * *' 42 | dryRun = true 43 | 44 | if(!('ghprb' in Jenkins.instance.pluginManager.plugins*.shortName)) { 45 | throw new Exception('GitHub Pull-Request Builder Plugin is not installed. This script has aborted.') 46 | } 47 | 48 | Jenkins.instance.getAllItems(Job.class).findAll { Job j -> 49 | j.getTrigger(GhprbTrigger.class) 50 | }.each { Job j -> 51 | if(!dryRun) { 52 | //for this job which has GHPRB enabled do the following: 53 | Trigger trigger = j.getTrigger(GhprbTrigger.class) 54 | //disable cron polling 55 | Field cron_field = trigger.getClass().getDeclaredField('cron') 56 | cron_field.setAccessible(true) 57 | cron_field.set(trigger, (pollToWebhooks)? '' : cron_field) 58 | //enable managing webhooks for the repository 59 | Field useGitHubHooks_field = trigger.getClass().getDeclaredField('useGitHubHooks') 60 | useGitHubHooks_field.setAccessible(true) 61 | useGitHubHooks_field.set(trigger, pollToWebhooks) 62 | //save the job configuration 63 | j.save() 64 | //start the trigger so webhooks are active in Jenkins and registered with the GitHub project via GitHub API 65 | trigger.start(j, false) 66 | } 67 | println "${(dryRun) ? 'DRYRUN: ' : ''}${j.name} converted." 68 | } 69 | //discard script console result output 70 | null 71 | -------------------------------------------------------------------------------- /disable-jenkins-cli-permanently.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | /* 23 | This script completely disables Jenkins CLI and ensures that it will remain 24 | disabled when Jenkins is restarted. 25 | 26 | - Installs disable-jenkins-cli.groovy script to JENKINS_HOME/init.groovy.d 27 | - Evaluates disable-jenkins-cli.groovy to patch the Jenkins runtime so no 28 | restart is required. 29 | */ 30 | 31 | import java.security.MessageDigest 32 | import jenkins.model.Jenkins 33 | 34 | //downloadFile and sha256sum copied from sandscape 35 | //https://github.com/sandscape/sandscape/blob/master/scripts/functions.groovy 36 | void downloadFile(String url, String fullpath) { 37 | new File(fullpath).with { f -> 38 | //make parent directories if they don't exist 39 | if(!f.parentFile.exists()) { 40 | f.parentFile.mkdirs() 41 | } 42 | f.newOutputStream().with { fos -> 43 | fos << new URL(url).openStream() 44 | fos.close() 45 | } 46 | } 47 | } 48 | 49 | //can take a String or File as an argument 50 | String sha256sum(def input) { 51 | MessageDigest.getInstance('SHA-256').digest(input.bytes).encodeHex().toString() 52 | } 53 | 54 | //main method 55 | String remote_jenkins_cli_script = 'https://raw.githubusercontent.com/samrocketman/jenkins-script-console-scripts/master/disable-jenkins-cli.groovy' 56 | String local_jenkins_cli_script = "${Jenkins.instance.root}/init.groovy.d/disable-jenkins-cli.groovy" 57 | downloadFile(remote_jenkins_cli_script, local_jenkins_cli_script) 58 | new File(local_jenkins_cli_script).with { f -> 59 | if(sha256sum(f) == 'ce649b6dc15837d7248cbf5ade057718aedfcbbe1188e25e7ee3b2b259401cb4') { 60 | println "Installed ${local_jenkins_cli_script} (patch persists on Jenkins restart)." 61 | try { 62 | evaluate(f) 63 | println 'Runtime has been patched to disable Jenkins CLI. No restart necessary.' 64 | } catch(Exception e) { 65 | println "ERROR: Runtime patching has failed. Removed ${local_jenkins_cli_script}" 66 | f.delete() 67 | throw e 68 | } 69 | } else { 70 | println 'ERROR: Disable Jenkins CLI script checksum mismatch; Aborting.' 71 | f.delete() 72 | } 73 | } 74 | null 75 | -------------------------------------------------------------------------------- /jenkins-stats.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | /* 23 | This script generates high level Jenkins statistics for Jenkins instances 24 | using Jervis. 25 | */ 26 | 27 | import com.cloudbees.hudson.plugins.folder.Folder 28 | import groovy.transform.Field 29 | import hudson.model.Job 30 | import hudson.model.User 31 | import jenkins.model.Jenkins 32 | import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject 33 | 34 | import jenkins.plugins.git.GitTagSCMHead 35 | import org.jenkinsci.plugins.github_branch_source.PullRequestSCMHead 36 | import org.jenkinsci.plugins.workflow.multibranch.BranchJobProperty 37 | 38 | 39 | //globally scoped vars 40 | j = Jenkins.instance 41 | @Field HashMap count_by_type = [:] 42 | count = j.getAllItems(Job.class).size() 43 | jobs_with_builds = j.getAllItems(Job.class)*.getNextBuildNumber().findAll { it > 1 }.size() 44 | global_total_builds = j.getAllItems(Job.class)*.getNextBuildNumber().sum { ((int) it) - 1 } 45 | j.getAllItems().each { i -> 46 | count_by_type[i.class.simpleName.toString()] = (count_by_type[i.class.simpleName.toString()])? count_by_type[i.class.simpleName.toString()]+1 : 1 47 | } 48 | 49 | List known_users = User.getAll()*.id 50 | organizations = j.getAllItems(Folder.class).findAll { !(it.name in known_users) }.size() 51 | projects = j.getAllItems(WorkflowMultiBranchProject.class).size() 52 | total_users = User.getAll().size() 53 | 54 | total_pull_requests = j.getAllItems(WorkflowMultiBranchProject.class)*.getAllItems(Job.class).flatten().findAll { 55 | it.getProperty(BranchJobProperty)?.branch?.head in PullRequestSCMHead 56 | }*.getNextBuildNumber().sum { 57 | ((int) it) - 1 58 | } 59 | 60 | total_tag_releases = j.getAllItems(WorkflowMultiBranchProject.class)*.getAllItems(Job.class).flatten().findAll { 61 | it.getProperty(BranchJobProperty)?.branch?.head in GitTagSCMHead 62 | }*.getNextBuildNumber().sum { 63 | ((int) it) - 1 64 | } 65 | 66 | //display the information 67 | println "Number of GitHub Organizations: ${organizations}" 68 | println "Number of GitHub Projects: ${projects}" 69 | println "Number of Jenkins jobs: ${count}" 70 | println "Jobs with more than one build: ${jobs_with_builds}" 71 | println "Number of users: ${total_users}" 72 | println "Global total number of builds: ${global_total_builds}" 73 | println "Global total number of pull requests executed: ${total_pull_requests}" 74 | println "Global total number of tag releases executed: ${total_tag_releases}" 75 | println "Count of projects by type." 76 | count_by_type.each { 77 | println " ${it.key}: ${it.value}" 78 | } 79 | //null because we don't want a return value in the Script Console 80 | null 81 | -------------------------------------------------------------------------------- /find-all-projects-using-label.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This reveals in Jenkins which projects are using a Jenkins agent with a 23 | particular label. Useful for finding all projects using a particular set of 24 | agents. 25 | */ 26 | import hudson.model.Job 27 | 28 | if(!binding.hasVariable('labels')) { 29 | labels = ['language:shell', 'language:ruby'] 30 | } 31 | if(!binding.hasVariable('evaluateOr')) { 32 | //by default it searches for any of the labels from the list 33 | //set evaluateAnd to true to require all labels much exist in the job 34 | evaluateOr = true 35 | } 36 | if(!binding.hasVariable('formatJervis')) { 37 | //Are Jenkins jobs organized when generated by Jervis? 38 | //https://github.com/samrocketman/jervis 39 | formatJervis = false 40 | } 41 | 42 | if(labels in String) { 43 | labels = (labels.contains(',')) ? labels.split(',') : [labels] 44 | } 45 | if(evaluateOr in String) { 46 | evaluateOr = (evaluateOr != 'false') 47 | } 48 | if(formatJervis in String) { 49 | formatJervis = (formatJervis != 'false') 50 | } 51 | 52 | //type check user defined parameters/bindings 53 | if(!(labels in List) || (false in labels.collect { it in String } )) { 54 | throw new Exception('PARAMETER ERROR: labels must be a list of strings.') 55 | } 56 | if(!(evaluateOr in Boolean)) { 57 | throw new Exception('PARAMETER ERROR: evaluateOr must be a boolean.') 58 | } 59 | if(!(formatJervis in Boolean)) { 60 | throw new Exception('PARAMETER ERROR: formatJervis must be a boolean.') 61 | } 62 | 63 | projects = [] as Set 64 | //getAllItems searches a global lookup table of items regardless of folder structure 65 | Jenkins.instance.getAllItems(Job.class).each { Job job -> 66 | Boolean labelFound = false 67 | String jobLabelString 68 | if(job.class.simpleName == 'FreeStyleProject') { 69 | jobLabelString = job.getAssignedLabelString() 70 | } else if(job.class.simpleName == 'WorkflowJob') { 71 | jobLabelString = job.getDefinition().getScript() 72 | } else { 73 | throw new Exception("Don't know how to handle class: ${job.getClass()}") 74 | } 75 | List results = labels.collect { label -> 76 | jobLabelString.contains(label) 77 | } 78 | 79 | if(evaluateOr) { 80 | //evaluate if any of the labels exist in job 81 | labelFound = true in results 82 | } else { 83 | //evaluate requiring all labels to exist in job 84 | labelFound = !(false in results) 85 | } 86 | 87 | if(labelFound) { 88 | if(formatJervis) { 89 | projects << "${job.fullName.split('/')[0]}/${job.displayName.split(' ')[0]}" 90 | } else { 91 | projects << job.fullName 92 | } 93 | } 94 | } 95 | println(projects.join('\n')) 96 | //null so no result shows up 97 | null 98 | -------------------------------------------------------------------------------- /find-all-cron-schedules-by-frequency.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Find all cron schedules in a Jenkins instance organized by frequency 23 | (weekly, daily, hourly, other). This script is compatible with all Jenkins 24 | job types and recurses into Jenkins folders. 25 | 26 | This script was inspired by: 27 | https://gist.github.com/mgedmin/d6d271f4ee6ae82d9a86 28 | */ 29 | 30 | import hudson.model.Descriptor 31 | import hudson.model.Job 32 | import hudson.triggers.TimerTrigger 33 | import java.util.regex.Pattern 34 | import jenkins.model.Jenkins 35 | 36 | /* 37 | FUNCTIONS 38 | */ 39 | 40 | boolean patternMatchesAnyLine(String pattern, String text) { 41 | text.tokenize('\n').any { String line -> 42 | Pattern.compile(pattern).matcher(line.trim()).matches() 43 | } 44 | } 45 | 46 | boolean isWeeklySchedule(String cron) { 47 | patternMatchesAnyLine('^([^ ]+ +){2}\\* +\\* +[^*]+$', cron) 48 | } 49 | 50 | boolean isDailySchedule(String cron) { 51 | patternMatchesAnyLine('^([^ *]+ +){2}\\* +\\* +\\*+$', cron) 52 | } 53 | 54 | boolean isHourlySchedule(String cron) { 55 | patternMatchesAnyLine('^[^ ]+ +\\* +\\* +\\*+$', cron) 56 | } 57 | 58 | void displaySchedule(String frequency, Map schedules) { 59 | if(!schedules) { 60 | return 61 | } 62 | int half = ((80 - frequency.size()) / 2) - 1 63 | int padding = 80 - (2*half + frequency.size()) - 2 64 | println(['='*half, frequency, '='*half].join(' ') + '='*padding) 65 | schedules.each { k, v -> 66 | println(k) 67 | println(' ' + v.tokenize('\n').join('\n ')) 68 | } 69 | println('='*80) 70 | } 71 | 72 | /* 73 | MAIN LOGIC 74 | */ 75 | 76 | 77 | // Find all schedules. 78 | Jenkins j = Jenkins.instance 79 | Descriptor cron = j.getDescriptor(TimerTrigger) 80 | Map schedules = j.getAllItems(Job).findAll { Job job -> 81 | job?.triggers?.get(cron) 82 | }.collect { Job job -> 83 | [ (job.fullName): job.triggers.get(cron).spec ] 84 | }.sum() 85 | 86 | 87 | // Gather schedules organized by their frequency. 88 | Map weekly = schedules.findAll { k, v -> 89 | isWeeklySchedule(v) 90 | } ?: [:] 91 | Map daily = schedules.findAll { k, v -> 92 | isDailySchedule(v) 93 | } ?: [:] 94 | Map hourly = schedules.findAll { k, v -> 95 | isHourlySchedule(v) 96 | } ?: [:] 97 | Map other = schedules.findAll { k, v -> 98 | !isWeeklySchedule(v) && 99 | !isDailySchedule(v) && 100 | !isHourlySchedule(v) 101 | } ?: [:] 102 | 103 | 104 | // Print out schedules after they're organized. 105 | displaySchedule('Weekly Schedules', weekly) 106 | displaySchedule('Daily Schedules', daily) 107 | displaySchedule('Hourly Schedules', hourly) 108 | displaySchedule('Other Schedules', other) 109 | -------------------------------------------------------------------------------- /notify-github-pr-comment.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Post a comment to GitHub or update an existing comment. 23 | Features: 24 | - Credentials stored in Jenkins. 25 | - Takes advantage of core Groovy features without utilizing any plugins 26 | other than the credenials plugin. 27 | */ 28 | import com.cloudbees.plugins.credentials.SystemCredentialsProvider 29 | import groovy.json.JsonOutput 30 | import groovy.json.JsonSlurper 31 | import hudson.util.Secret 32 | import java.util.regex.Pattern 33 | import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl 34 | 35 | Secret getStringCredential(String id) { 36 | Secret secret = SystemCredentialsProvider.getInstance().credentials.find { 37 | it in StringCredentialsImpl && it.id == id 38 | }?.secret 39 | if(!secret) { 40 | throw new RuntimeException("Could not find a String Credential with the id '${id}'") 41 | } 42 | secret 43 | } 44 | 45 | Integer findCommentIdByUser(String credential, String repository, String pr_number, String username, String expression = '') { 46 | Secret gh_token = getStringCredential(credential) 47 | String api_endpoint = "https://api.github.com/repos/${repository}/issues/${pr_number}/comments" 48 | Reader reader = new URL(api_endpoint).newReader(requestProperties: ['Authorization': "token ${gh_token}".toString(), 'Accept': 'application/vnd.github.v3+json']) 49 | new JsonSlurper().parse(reader).find { 50 | it?.user?.login == username && ( 51 | !expression || Pattern.compile(expression, Pattern.MULTILINE | Pattern.DOTALL).matcher(it?.body).matches() 52 | ) 53 | }?.id ?: 0 54 | } 55 | 56 | void postGHPRComment(String credential, String repository, String pr_number, String message, Integer comment_id = 0) { 57 | Secret gh_token = getStringCredential(credential) 58 | String method = 'POST' 59 | Map data = [ 60 | body: message 61 | ] 62 | String api_endpoint = "https://api.github.com/repos/${repository}/issues" 63 | if(comment_id) { 64 | api_endpoint += "/comments/${comment_id}" 65 | method = 'PATCH' 66 | } 67 | else { 68 | api_endpoint += "/${pr_number}/comments" 69 | } 70 | String result = new URL(api_endpoint).openConnection().with { 71 | doOutput = true 72 | requestMethod = method 73 | setRequestProperty('Authorization', "token ${gh_token}".toString()) 74 | setRequestProperty('Accept', 'application/vnd.github.v3+json') 75 | outputStream.withWriter { writer -> 76 | writer << JsonOutput.toJson(data) 77 | } 78 | content.text 79 | } 80 | //nothing to do with result 81 | } 82 | 83 | String message = ''' 84 | # heading 1 85 | 86 | This is a new kind of comment. 87 | '''.trim() 88 | String pattern = '^# heading 1.*' 89 | int id = findCommentIdByUser('github-token', 'samrocketman/jervis-example-project', '6', 'samrocketman', pattern) 90 | postGHPRComment('github-token', 'samrocketman/jervis-example-project', '6', message, id) 91 | -------------------------------------------------------------------------------- /find-all-jobs-cloning-github-repository.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script will search a Jenkins instance for all freestyle jobs, pipeline 23 | jobs, or multibranch pipeline jobs which have the repository configured to 24 | be cloned. This script is useful for discovering jobs which might affect a 25 | given repository. 26 | */ 27 | 28 | // enter the repository to search 29 | String repository = 'namespace/example-repository' 30 | 31 | // core classes 32 | import hudson.model.Job 33 | import hudson.model.Item 34 | import hudson.scm.NullSCM 35 | import jenkins.model.Jenkins 36 | import java.util.regex.Pattern 37 | import hudson.model.FreeStyleProject 38 | 39 | /* 40 | The following commented out classes are here because we use them, but they 41 | are provided by a plugin which may not be installed in a Jenkins instance. 42 | They're discovered without importing them. 43 | */ 44 | //import org.jenkinsci.plugins.multiplescms.MultiSCM 45 | //import org.jenkinsci.plugins.workflow.job.WorkflowJob 46 | //import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject 47 | 48 | Pattern pattern = Pattern.compile(".*\\Q${repository}\\E(\\.git)?\$") 49 | 50 | List getMatchingMultibranchPipelineUrls(Pattern p) { 51 | Jenkins.instance.getAllItems(Item).findAll { Item j -> 52 | j.class.simpleName == 'WorkflowMultiBranchProject' 53 | }.findAll { Item j -> 54 | j.getSCMSources().find { 55 | it.class.simpleName == 'GitHubSCMSource' 56 | }?.with { 57 | p.matcher("${it.repoOwner}/${it.repository}").matches() 58 | } 59 | }.collect { Item j -> 60 | "${Jenkins.instance.rootUrl}${j.url}" 61 | } 62 | } 63 | 64 | List getMatchingStandalonePipelineUrls(Pattern p) { 65 | Jenkins.instance.getAllItems(Job).findAll { Job j -> 66 | j.class.simpleName == 'WorkflowJob' && 67 | j.parent.class.simpleName != 'WorkflowMultiBranchProject' 68 | }.findAll { Job j -> 69 | j.definition?.scm?.userRemoteConfigs*.url.any { String s -> 70 | p.matcher(s).matches() 71 | } 72 | }.collect { Job j -> 73 | "${Jenkins.instance.rootUrl}${j.url}" 74 | } 75 | } 76 | 77 | List getMatchingFreeStyleJobUrls(Pattern p) { 78 | Jenkins.instance.getAllItems(FreeStyleProject).findAll { Job j -> 79 | if(j.scm in NullSCM) { 80 | return false 81 | } 82 | j.scm.with { scm -> 83 | if(scm.class.simpleName == 'MultiSCM') { 84 | return scm.configuredSCMs*.userRemoteConfigs.flatten() 85 | } 86 | j.scm?.userRemoteConfigs ?: [] 87 | }*.url.any { String s -> 88 | p.matcher(s).matches() 89 | } 90 | }.collect { Job j -> 91 | "${Jenkins.instance.rootUrl}${j.url}" 92 | } 93 | } 94 | 95 | List findAllMatchingJobUrls(Pattern p) { 96 | [ 97 | getMatchingFreeStyleJobUrls(p), 98 | getMatchingMultibranchPipelineUrls(p), 99 | getMatchingStandalonePipelineUrls(p) 100 | ].flatten().sort() 101 | } 102 | 103 | '\n* ' + (findAllMatchingJobUrls(pattern).join('\n* ') ?: 'No jobs found') 104 | -------------------------------------------------------------------------------- /user-contributed-examples/FindAndAbortJobsByNameAndClass.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | Author: https://github.com/ranma2913 3 | Warning: This code is unlicensed. 4 | See https://github.com/samrocketman/jenkins-script-console-scripts/blob/master/user-contributed-examples/README.md 5 | */ 6 | 7 | 8 | import hudson.* 9 | import hudson.model.* 10 | import jenkins.* 11 | import jenkins.model.* 12 | import org.jenkinsci.plugins.workflow.job.* 13 | 14 | // jobNameSearchParam should be absolute OR end in a slash. The result must be an instance of 15 | def jobNameSearchParam = "RIPTiDE_SonarQube/SonarQube_PullRequest_Scanner/" 16 | //def jobNameSearchParam = "RIPTiDE_SonarQube/SonarQube_PullRequest_Scanner/dashboard-launcher/Harish_Testing_branch" 17 | def abortJobs = false 18 | def classesToAbort = [ 19 | org.jenkinsci.plugins.workflow.job.WorkflowJob 20 | ] 21 | 22 | def itemsNotProcessed = [:] 23 | def jobsFoundMatchingSearch = 0 24 | def jobsFoundInClassesToAbort = 0 25 | def jobsAborted = 0 26 | 27 | println(""" 28 | ************************************************** START ***************************************************************" + 29 | jobNameSearchParam: '${jobNameSearchParam}' 30 | abortJobs: ${abortJobs} 31 | classesToAbort: ${classesToAbort} 32 | jobsFoundMatchingSearch: ${jobsFoundMatchingSearch} 33 | jobsFoundInClassesToAbort: ${jobsFoundInClassesToAbort} 34 | jobsAborted: ${jobsAborted} 35 | """.trim()) 36 | println("""****************************************** Processing Started ********************************************************** 37 | Searching through AbstractItems...""") 38 | 39 | //JavaDoc: https://javadoc.jenkins-ci.org/hudson/model/AbstractItem.html 40 | Jenkins.instance.getAllItems(AbstractItem.class).each { item -> 41 | if (item.fullName.contains(jobNameSearchParam)) { 42 | jobsFoundMatchingSearch++ 43 | if (classesToAbort.contains(item.getClass())) { 44 | jobsFoundInClassesToAbort++ 45 | WorkflowJob workflowJob = Jenkins.instance.getItemByFullName(item.fullName) 46 | println("Class: ${item.getClass()}\nfullName: ${item.fullName}") 47 | 48 | println("WorkflowJob RunMap.size(${workflowJob._getRuns().size()}):") 49 | //JavaDoc: https://javadoc.jenkins.io/plugin/workflow-job/org/jenkinsci/plugins/workflow/job/WorkflowRun.html 50 | workflowJob._getRuns().each { int runId, WorkflowRun build -> 51 | println("""|\t- runId: ${runId} 52 | |\t BuildStatusSumamry.message: ${build.getBuildStatusSummary().message} 53 | |\t Build.hasAllowKill(): ${build.hasAllowKill()} 54 | |\t Build.hasAllowTerm(): ${build.hasAllowTerm()} 55 | |\t Build.hasntStartedYet(): ${build.hasntStartedYet()} 56 | |\t Build.isBuilding(): ${build.isBuilding()} 57 | |\t Build.isInProgress(): ${build.isInProgress()} 58 | |\t Build.isLogUpdated(): ${build.isLogUpdated()} 59 | |\t build: ${build}""".stripMargin() + '\n') 60 | if (abortJobs && (build.isBuilding() || build.isInProgress())) { 61 | build.finish(hudson.model.Result.ABORTED, 62 | new IOException(""" 63 | |Build Aborted by Jenkins Script Console 64 | |User: ${User.current().getId()} (${User.current()}) 65 | |Build: ${build} 66 | |Stack Trace:""".stripMargin() 67 | ) 68 | ) 69 | jobsAborted++ 70 | } 71 | } 72 | 73 | // Add Newline between each WorkflowJob group. 74 | println("\n") 75 | } 76 | } else { 77 | def itemListForType = itemsNotProcessed.get("${item.getClass()}".toString(), []) 78 | itemListForType.add(item) 79 | itemsNotProcessed.put("${item.getClass()}".toString(), itemListForType) 80 | } 81 | } 82 | 83 | println(""" 84 | ***************************************** Processing Complete **********************************************************" + 85 | jobNameSearchParam: '${jobNameSearchParam}' 86 | abortJobs: ${abortJobs} 87 | classesToAbort: ${classesToAbort} 88 | jobsFoundMatchingSearch: ${jobsFoundMatchingSearch} 89 | jobsFoundInClassesToAbort: ${jobsFoundInClassesToAbort} 90 | jobsAborted: ${jobsAborted}""") 91 | println("\nitemsNotProcessed (wrong type or not matching jobNameSearchParam):") 92 | itemsNotProcessed.each { itemType, itemListForType -> 93 | println("itemType: ${itemType}") 94 | itemListForType.each { item -> 95 | println("\t - ${item}") 96 | } 97 | } 98 | 99 | return "************************************************* END ******************************************************************" 100 | -------------------------------------------------------------------------------- /export-jenkins-credentials.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script does a credential export which is compatible with 23 | https://github.com/samrocketman/jenkins-bootstrap-shared/blob/master/scripts/credentials-multitype.groovy 24 | */ 25 | import com.cloudbees.plugins.credentials.SystemCredentialsProvider 26 | import com.cloudbees.plugins.credentials.domains.Domain 27 | import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl 28 | import hudson.util.Secret 29 | import jenkins.model.Jenkins 30 | 31 | SystemCredentialsProvider creds_config = Jenkins.instance.getExtensionList(SystemCredentialsProvider).first() 32 | 33 | List creds = creds_config.getDomainCredentialsMap().get(Domain.global()) 34 | 35 | def getCredential(def cred) { 36 | Map credential = [:] 37 | switch(cred.class.simpleName) { 38 | case 'AWSCredentialsImpl': 39 | credential = [ 40 | credential_type: 'AWSCredentialsImpl', 41 | credentials_id: cred.id, 42 | description: cred.description ?: '', 43 | access_key: cred.accessKey ?: '', 44 | secret_key: cred.secretKey.plainText ?: '', 45 | iam_role_arn: cred.iamRoleArn ?: '', 46 | iam_mfa_serial_number: cred.iamMfaSerialNumber ?: '', 47 | scope: cred.scope.toString().toLowerCase() 48 | ] 49 | break 50 | case 'BasicSSHUserPrivateKey': 51 | credential = [ 52 | credential_type: 'BasicSSHUserPrivateKey', 53 | credentials_id: cred.id, 54 | description: cred.description ?: '', 55 | user: cred.username ?: '', 56 | key_passwd: cred.passphrase?.plainText ?: '', 57 | key: ((cred.privateKey instanceof Secret) ? cred.privateKey.plainText : cred.privateKey) ?: '', 58 | scope: cred.scope.toString().toLowerCase() 59 | ] 60 | break 61 | case 'UsernamePasswordCredentialsImpl': 62 | credential = [ 63 | credential_type: 'UsernamePasswordCredentialsImpl', 64 | credentials_id: cred.id, 65 | description: cred.description ?: '', 66 | user: cred.username ?: '', 67 | password: cred.password.plainText ?: '', 68 | scope: cred.scope.toString().toLowerCase() 69 | ] 70 | break 71 | case 'StringCredentialsImpl': 72 | credential = [ 73 | credential_type: 'StringCredentialsImpl', 74 | credentials_id: cred.id, 75 | description: cred.description ?: '', 76 | secret: cred.secret.plainText ?: '', 77 | scope: cred.scope.toString().toLowerCase() 78 | ] 79 | break 80 | case 'GitHubAppCredentials': 81 | credential = [ 82 | credential_type: 'GitHubAppCredentials', 83 | credentials_id: cred.id, 84 | description: cred.description ?: '', 85 | appid: cred.appID, 86 | apiuri: cred.apiUri, 87 | owner: cred.owner, 88 | key: ((cred.privateKey instanceof Secret) ? cred.privateKey.plainText : cred.privateKey) ?: '' 89 | ] 90 | break 91 | default: 92 | println "Missing support for '${cred.class.name}'." 93 | } 94 | credential 95 | } 96 | 97 | List credsMap = creds.collect { 98 | getCredential(it) 99 | }.findAll { it } 100 | 101 | // pretty print 102 | println('[') 103 | println(' ' + credsMap.collect { Map cred -> 104 | '[\n ' + 105 | cred.collect { k, v -> 106 | "${k}: ${v.inspect()}" 107 | }.join(',\n ') + 108 | '\n ]' 109 | }.join(',\n ')) 110 | println(']') 111 | -------------------------------------------------------------------------------- /find-latest-git-tag-releases-from-pipeline-stage-name.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /** 22 | Search all multibranch pipelines and return the latest tag release. If the 23 | build was successful and the pipeline contains a certain stage name, then it 24 | counts as a tag release build. 25 | 26 | To create this script I referenced the following source code for hints of 27 | available Jenkins APIs. 28 | 29 | https://gist.github.com/GuillaumeSmaha/fdef2088f7415c60adf95d44073c3c88 30 | */ 31 | import hudson.model.Result 32 | import jenkins.model.Jenkins 33 | import jenkins.plugins.git.GitTagSCMHead 34 | import org.jenkinsci.plugins.workflow.actions.LabelAction 35 | import org.jenkinsci.plugins.workflow.cps.nodes.StepEndNode 36 | import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode 37 | import org.jenkinsci.plugins.workflow.graph.FlowNode 38 | import org.jenkinsci.plugins.workflow.job.WorkflowJob 39 | import org.jenkinsci.plugins.workflow.job.WorkflowRun 40 | import org.jenkinsci.plugins.workflow.multibranch.BranchJobProperty 41 | 42 | /** 43 | User configurable variables. 44 | */ 45 | 46 | // a Git tag build must contain the following pipeline stage in order to be 47 | // considered a release build. 48 | String releaseStageName = 'Release to Nexus' 49 | 50 | /** 51 | Get the human readable Jenkins pipeline stages if given a build the current 52 | flow heads from a build. For example, build.execution.currentHeads. 53 | 54 | @param flowNodes A list of flow nodes to recursively search for the display 55 | name of stages. 56 | @param stages An optional argument used in recursion. This argument is 57 | used internally and there's no need for you to pass a list 58 | at all. 59 | @return A List of stage display names from a build. 60 | 61 | */ 62 | List getBuildStages(List flowNodes, List stages = []) { 63 | if(!flowNodes) { 64 | return stages.reverse() 65 | } 66 | flowNodes.each { FlowNode flowNode -> 67 | if(!(flowNode in StepEndNode)) { 68 | return 69 | } 70 | if(!flowNode.startNode.getAction(LabelAction)) { 71 | return 72 | } 73 | stages << flowNode.startNode.displayName 74 | } 75 | getBuildStages(flowNodes[-1]?.parents, stages) 76 | } 77 | 78 | /** 79 | Find a job containing a successful build whose pipeline contains a specific stage name. 80 | 81 | @param job A Jenkins pipeline job object. 82 | @param stageName A stage name the pipeline job must contain. 83 | 84 | @return A build which was the result of a successful release. 85 | */ 86 | WorkflowRun getSuccessfulReleaseBuild(WorkflowJob job, String stageName) { 87 | job.builds.find { WorkflowRun build -> 88 | (build?.result == Result.SUCCESS) && 89 | (stageName in getBuildStages(build.execution.currentHeads)) 90 | } 91 | } 92 | 93 | Map latestSuccessfulReleases = [:] 94 | 95 | // Find the latest successful Git tag releases for all jobs that contain a 96 | // 'Release to Nexus' stage name in its most recent build. 97 | Jenkins.instance.getAllItems(WorkflowJob).findAll { WorkflowJob job -> 98 | WorkflowRun build = job.builds?.first() 99 | (job?.getProperty(BranchJobProperty)?.branch?.head in GitTagSCMHead) && 100 | getSuccessfulReleaseBuild(job, releaseStageName) 101 | }.sort { WorkflowJob job -> 102 | WorkflowRun build = getSuccessfulReleaseBuild(job, releaseStageName) 103 | // sort by time a job ended 104 | build.startTimeInMillis + build.duration 105 | }.each { WorkflowJob job -> 106 | if(job.name in latestSuccessfulReleases.keySet()) { 107 | return 108 | } 109 | latestSuccessfulReleases[job.parent.name] = job.name 110 | } 111 | 112 | // print the latest successful releases 113 | latestSuccessfulReleases.each { k, v -> 114 | println "${k} ${v}" 115 | } 116 | null 117 | -------------------------------------------------------------------------------- /generate-unix-jnlp-agent-script.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Generate a bash shell script meant to run on Unix-like systems. The shell 23 | script will start a static JNLP agent. 24 | */ 25 | 26 | import groovy.text.SimpleTemplateEngine 27 | import java.security.MessageDigest 28 | import java.util.regex.Matcher 29 | import java.util.regex.Pattern 30 | 31 | if(!binding.hasVariable('agent_name')) { 32 | //binding doesn't exist so set a default 33 | agent_name = 'my-agent' 34 | } 35 | //type check user defined parameters/bindings 36 | if(!(agent_name instanceof String)) { 37 | throw new Exception('PARAMETER ERROR: agent_name must be a string.') 38 | } 39 | 40 | //script bindings 41 | jenkins = Jenkins.instance 42 | node = jenkins.getNode(agent_name) 43 | computer = node.computer 44 | launcher = node.launcher 45 | 46 | String checksum( def input ) { 47 | MessageDigest.getInstance('SHA-256').digest(input.bytes).encodeHex().toString() 48 | } 49 | 50 | String getJenkinsUrl() { 51 | jenkins.rootUrl.trim().replaceAll('/$', '') as String 52 | } 53 | 54 | shell_script_template = '''#!/bin/bash 55 | #This shell script was automatically generated 56 | #https://github.com/samrocketman/jenkins-script-console-scripts/generate-unix-jnlp-agent-script.groovy 57 | #DESCRIPTION: 58 | # Start a JNLP agent 59 | 60 | #USAGE: 61 | # Start the agent: 62 | # bash agent.sh start 63 | # Stop the agent: 64 | # bash agent.sh stop 65 | 66 | JENKINS_URL="<%= jenkins_url %>" 67 | JENKINS_HOME="<%= jenkins_home %>" 68 | COMPUTER_URL="<%= computer_url %>" 69 | COMPUTER_SECRET="<%= computer_secret %>" 70 | JAVA_OPTS="<%= java_opts %>" 71 | OS_KERNEL="$(uname)" 72 | AGENT_PREFIX="${AGENT_PREFIX:-${JENKINS_HOME}}" 73 | AGENT_PID="${AGENT_PREFIX}/agent.pid" 74 | AGENT_LOG="${AGENT_PREFIX}/agent.log" 75 | 76 | if [ "$1" = "stop" ]; then 77 | cd "${JENKINS_HOME}" 78 | if [ ! -f "${AGENT_PID}" ]; then 79 | echo "No ${AGENT_PID}. Nothing to stop." 1>&2 80 | false 81 | else 82 | kill $(<"${AGENT_PID}") 83 | rm -f "${AGENT_PID}" 84 | fi 85 | exit $? 86 | elif [ "$#" -gt 0 -a ! "$1" = "start" ]; then 87 | echo "Invalid argument: $0 [start|stop]" 1>&2 88 | echo "No argument assumes $0 start" 1>&2 89 | exit 1 90 | fi 91 | set -uxe 92 | 93 | function download_url() { 94 | URL="$1" 95 | LOCAL_FILE="${1##*/}" 96 | if [ -x "$(type -p curl)" ]; then 97 | curl -f -o "${AGENT_PREFIX}/${LOCAL_FILE}" "${URL}" 98 | elif [ -x "$(type -p wget)" ]; then 99 | wget -O "${AGENT_PREFIX}/${LOCAL_FILE}" "${URL}" 100 | else 101 | echo "No supported download method found." 1>&2 102 | return 1 103 | fi 104 | } 105 | 106 | #pre-flight tests (if any fail then script will exit) 107 | id -un 108 | echo "JAVA_HOME=${JAVA_HOME}" 109 | mkdir -p "${JENKINS_HOME}/logs" 110 | cd "${JENKINS_HOME}" 111 | 112 | #download JNLP slave.jar 113 | JNLP_JAR_SHA="<%= jnlp_jar_sha %>" 114 | download_url "${JENKINS_URL}/jnlpJars/slave.jar" 115 | #verify integrity of download 116 | if [ -x "$(type -P sha256sum)" ]; then 117 | echo "${JNLP_JAR_SHA} slave.jar" | sha256sum -c - 118 | elif [ -x "$(type -P shasum)" ]; then 119 | echo "${JNLP_JAR_SHA} slave.jar" | shasum -a 256 -c - 120 | fi 121 | 122 | exec nohup "${JAVA_HOME}/bin/java" ${JAVA_OPTS} -jar slave.jar -jnlpUrl "${JENKINS_URL}/${COMPUTER_URL}/slave-agent.jnlp" -secret ${COMPUTER_SECRET} > "${AGENT_LOG}" 2>&1 & 123 | echo $! > "${AGENT_PID}"'''.replaceAll(Pattern.quote('$'), Matcher.quoteReplacement('\\$')) 124 | 125 | 126 | Map scriptBinding = [ 127 | jenkins_url: getJenkinsUrl(), 128 | jenkins_home: node.remoteFS.trim(), 129 | computer_url: computer.url.trim().replaceAll('/$', '') as String, 130 | computer_secret: computer.jnlpMac.trim(), 131 | java_opts: (launcher.vmargs)?:"", 132 | jnlp_jar_sha: checksum("${getJenkinsUrl()}/jnlpJars/slave.jar".toURL()) 133 | ] 134 | 135 | println (new SimpleTemplateEngine().createTemplate(shell_script_template).make(scriptBinding)) 136 | null 137 | -------------------------------------------------------------------------------- /generate-all-jervis-jobs.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /** 22 | This script searches all jobs in a Jenkins instance and regenerates them by 23 | building the Jervis generator job with parameters. It will automatically 24 | sort all projects by their recent builds and proceed from newest to oldest 25 | built projects. Useful for migrating Job DSL script changes as Jenkins is 26 | upgraded or Jervis is changed. 27 | 28 | The recommended way to operate this script is from a freestyle Jenkins job 29 | configured with an "Execute system Groovy script" build step. 30 | */ 31 | import hudson.model.Item 32 | import hudson.model.ParametersAction 33 | import hudson.model.StringParameterValue 34 | import jenkins.model.Jenkins 35 | import org.jenkinsci.plugins.github_branch_source.GitHubSCMSource 36 | import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject 37 | 38 | /* 39 | USER CONFIGURABLE VARIABLES 40 | */ 41 | 42 | // Default 0 seconds between retries. However, if you're regenerating a large 43 | // number of builds then you'll want to avoid API limits by throttling how 44 | // quickly jobs get generated. If you need to throttle, then a recommended 45 | // value is 20 seconds between jobs being generated. 46 | Integer secondsToWaitBetweenBuilds = 20 47 | 48 | // Optional: update this list of matching names and only projects which match a 49 | // given set of names will be included in the job generation. This is useful 50 | // if you want to isolate a set of projects by their job or folder name. A 51 | // matching name is an exact match or a partial name where the project starts 52 | // with the characters. 53 | List matchingNames = [] 54 | 55 | /* 56 | FUNCTIONS 57 | */ 58 | 59 | Integer getLastBuildTimestamp(Item item) { 60 | (item?.builds?.find { it }?.timestamp?.toInstant()?.epochSecond) ?: 0 61 | } 62 | 63 | List sortBranchesByLastBuild(WorkflowMultiBranchProject project) { 64 | project.items.sort { a, b -> 65 | Integer lastBuildA = getLastBuildTimestamp(a) 66 | Integer lastBuildB = getLastBuildTimestamp(b) 67 | if(!lastBuildA && !lastBuildB) { 68 | b.builds.size() <=> a.builds.size() 69 | } else { 70 | lastBuildB <=> lastBuildA 71 | } 72 | } 73 | } 74 | 75 | List sortProjectsByLastBuilt(List projects) { 76 | projects.sort { a, b -> 77 | Integer lastBuildA = getLastBuildTimestamp(sortBranchesByLastBuild(a).find { it }) 78 | Integer lastBuildB = getLastBuildTimestamp(sortBranchesByLastBuild(b).find { it }) 79 | if(!lastBuildA && !lastBuildB) { 80 | b.items.size() <=> a.items.size() 81 | } else { 82 | lastBuildB <=> lastBuildA 83 | } 84 | } 85 | } 86 | 87 | /* 88 | MAIN CODE 89 | */ 90 | 91 | def generator_job = Jenkins.instance.getItemByFullName('_jervis_generator') 92 | Integer milliSecondsToWait = secondsToWaitBetweenBuilds * 1000 93 | if(milliSecondsToWait && !binding.hasVariable('out')) { 94 | throw new Exception('You must run this script from a freestyle job configured with "Execute system Groovy script"') 95 | } 96 | 97 | sortProjectsByLastBuilt(Jenkins.instance.getAllItems(WorkflowMultiBranchProject).findAll { project -> 98 | !matchingNames || 99 | project.fullName.tokenize('/').any { path -> 100 | matchingNames.any { expr -> 101 | path.startsWith(expr) 102 | } 103 | } 104 | }).collect { project -> 105 | project.getSCMSources().find { source -> 106 | source in GitHubSCMSource 107 | }.getRepositoryUrl() -~ '^https://github.com/' 108 | }.unique().each { String project -> 109 | def actions = new ParametersAction([new StringParameterValue('project', project)]) 110 | generator_job.scheduleBuild2(0, actions) 111 | if(binding.hasVariable('out')) { 112 | out.println "Scheduled project ${project}." 113 | if(milliSecondsToWait) { 114 | out.println "Sleeping for ${secondsToWaitBetweenBuilds} seconds..." 115 | } 116 | // Thread.sleep instead of sleep allows aborting via Jenkins UI 117 | // https://stackoverflow.com/questions/46060040/java-thread-interruption-wont-work-for-me-in-groovy 118 | Thread.sleep(milliSecondsToWait) 119 | } 120 | else { 121 | println "Scheduled project ${project}." 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /delete-multibranch-pipelines-associated-with-archived-repositories.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /** 22 | Find all multibranch pipeline jobs which are configured with GitHub branch 23 | source. Generate GraphQL queries to query GitHub repository archived status. 24 | For every job associated with an archived GitHub repository, delete the 25 | multibranch pipeline job. 26 | 27 | By default will only print jobs which would be deleted. 28 | 29 | Assumes you have the following plugins installed: 30 | - GitHub Branch Source Plugin 31 | - Pipeline: Multibranch Plugin 32 | - SCM Filter Jervis Plugin 33 | */ 34 | import static net.gleske.jervis.tools.AutoRelease.getScriptFromTemplate 35 | import com.cloudbees.plugins.credentials.CredentialsMatchers 36 | import com.cloudbees.plugins.credentials.CredentialsProvider 37 | import hudson.security.ACL 38 | import jenkins.model.Jenkins 39 | import net.gleske.jervis.remotes.GitHubGraphQL 40 | import net.gleske.jervis.remotes.interfaces.TokenCredential 41 | import org.jenkinsci.plugins.plaincredentials.StringCredentials 42 | import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject 43 | import org.jenkinsci.plugins.github_branch_source.GitHubSCMSource 44 | 45 | // if false will really delete jobs 46 | Boolean dryRun = true 47 | 48 | class JenkinsTokenCredential implements TokenCredential { 49 | private String credentials_id 50 | JenkinsTokenCredential(String id) { 51 | this.credentials_id = id 52 | } 53 | String getToken() { 54 | List credentials = CredentialsProvider.lookupCredentials(StringCredentials, Jenkins.getInstance(), ACL.SYSTEM) 55 | StringCredentials found_credentials = CredentialsMatchers.firstOrNull(credentials, CredentialsMatchers.withId(this.credentials_id)) 56 | if(!found_credentials) { 57 | throw new Exception("ERROR: could not find Jenkins string credential with ID ${this.credentials_id}") 58 | } 59 | found_credentials.secret.plainText 60 | } 61 | void setToken(String s) { 62 | //discard 63 | null 64 | } 65 | } 66 | 67 | Map repos = Jenkins.instance.getAllItems(WorkflowMultiBranchProject).findAll { 68 | it.sources.find { scm -> 69 | scm.source in GitHubSCMSource 70 | } 71 | }.collect { job -> 72 | job.sources.find { scm -> 73 | scm.source in GitHubSCMSource 74 | }.source.with { 75 | [ 76 | (job.fullName): [ 77 | owner: it.repoOwner, 78 | name: it.repository, 79 | repoWithOwner: it.repoOwner + '/' + it.repository 80 | ] 81 | ] 82 | } 83 | }.sum() 84 | 85 | Map groupBy100 = [:] 86 | int limit = 100 87 | int group = 0 88 | int count = 0 89 | repos.each { k, v -> 90 | if(!(count++ % limit)) { 91 | group++ 92 | } 93 | if(!groupBy100[group]) { 94 | groupBy100[group] = [:] 95 | } 96 | groupBy100[group][(k)] = v 97 | } 98 | 99 | String graphql_template = ''' 100 | query {<% int i = 0; repos.each { nameWithOwner, repo -> %> 101 | repo${i++}: repository(owner: "${repo.owner}", name: "${repo.name}") { 102 | nameWithOwner 103 | isArchived 104 | }<% } %> 105 | } 106 | '''.trim() 107 | 108 | List queries = groupBy100.collect { k, v -> getScriptFromTemplate(graphql_template, [repos: v]) } 109 | List archivedRepos = [] 110 | 111 | GitHubGraphQL github = new GitHubGraphQL() 112 | github.credential = new JenkinsTokenCredential('github-token') 113 | limit = 300 114 | count = 0 115 | queries.each { String query -> 116 | int retryWait = 3 117 | while(true) { 118 | try { 119 | github.sendGQL(query).data.each { k, repo -> 120 | if(repo.isArchived) { 121 | archivedRepos << repo.nameWithOwner 122 | } 123 | } 124 | break 125 | } catch(Exception e) { 126 | count++ 127 | if(count > limit) { 128 | // Useful if we will indefinitely encounter exceptions. 129 | // Retrying GraphQL requests should be relatively rare. 130 | throw e 131 | } 132 | int sleepInterval = (new Random()).nextInt(retryWait) + 1 133 | println("Will try GitHub communication again after sleeping for ${sleepInterval} seconds.") 134 | sleep(retryWait*1000) 135 | retryWait = (retryWait + 3) % 100 136 | //retry request indefinitely changing up random sleep intervals 137 | } 138 | } 139 | } 140 | println("${(dryRun ? 'DRYRUN: ' : '')}Number of jobs related to archived repositories: ${archivedRepos.size()}") 141 | List jenkinsJobsToDelete = repos.findAll { k, v -> 142 | v.repoWithOwner in archivedRepos 143 | }.collect { k, v -> k } 144 | println(" ${jenkinsJobsToDelete.join('\n ')}") 145 | 146 | if(!dryRun) { 147 | jenkinsJobsToDelete.each { 148 | Jenkins.instance.getItemByFullName(it).delete() 149 | println("Deleted job ${it}") 150 | } 151 | } 152 | if(!dryRun) { 153 | println("${jenkinsJobsToDelete.size()} jenkins jobs have been deleted related to archived GitHub repositories.") 154 | } 155 | else { 156 | println 'Nothing was deleted because dryRun is enabled.' 157 | } 158 | -------------------------------------------------------------------------------- /cleanup-data-on-all-agents-threaded.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | This script will iterate across all static agents and clean up specified 23 | directories older than 1 day. 24 | 25 | This script is meant to be run from a job using the System Groovy build 26 | step. 27 | */ 28 | 29 | import hudson.util.RemotingDiagnostics 30 | import java.util.concurrent.locks.ReentrantLock 31 | import jenkins.model.Jenkins 32 | 33 | find_command_list = [ 34 | "timeout 1800 find /home/jenkins/.m2 -mtime +1 -type d -name '*-SNAPSHOT' -prune", 35 | "timeout 1800 find /tmp -maxdepth 1 -mtime +1 -type d \\\\( -name 'tmp[_0-9a-zA-Z]*' -o -name 'npm-*' \\\\) -prune", 36 | "timeout 1800 find /tmp/phantomjs -maxdepth 1 -mtime +1 -name 'phantomjs-*' -prune" 37 | ] 38 | 39 | //user configurable variable; set dryRun=false to really delete files 40 | if('DRY_RUN' in build.envVars) { 41 | dryRun = build.envVars['DRY_RUN'] 42 | } 43 | if(!binding.hasVariable('dryRun')) { 44 | dryRun = true 45 | } 46 | if(dryRun instanceof String) { 47 | dryRun = (dryRun != "false") 48 | } 49 | 50 | if(!dryRun) { 51 | //find command should run rm on the result to delete said found directories 52 | find_command_list = find_command_list.collect { it + ' -exec timeout 10 rm -rf {} \\\\;' } 53 | } 54 | 55 | //customize withLock and create a script binding lock variable to be shared across threads 56 | ReentrantLock.metaClass.withLock = { 57 | lock() 58 | try { 59 | it() 60 | } finally { 61 | unlock() 62 | } 63 | } 64 | lock = new ReentrantLock() 65 | 66 | //create a bash script to be loaded and executed on each agent 67 | bash_script = """ 68 | #!/bin/bash 69 | #Generated by a Jenkins job created by Sam Gleske 70 | 71 | #delete self after script is run 72 | function cleanup_on() { 73 | [ -d "\\\${TMPPATH}" ] && rm -rf "\\\${TMPPATH}" 74 | rm -f "\\\$0" 75 | } 76 | trap cleanup_on EXIT 77 | set -x 78 | #if timeout is not installed, then temporarily create one based on perl 79 | if ! type -P timeout; then 80 | export TMPPATH="\\\$(mktemp -d)" 81 | cat > "\\\${TMPPATH}/timeout" <<'EOF' 82 | #!/bin/bash 83 | perl -e 'alarm shift; exec @ARGV' "\\\$@" 84 | EOF 85 | chmod 755 "\\\${TMPPATH}/timeout" 86 | export PATH="\\\${TMPPATH}:\\\${PATH}" 87 | fi 88 | ${find_command_list.join('\n')} 89 | """.trim() 90 | 91 | //prepare to execute the bash script on remote agents 92 | groovy_script = """ 93 | script="mktemp /tmp/cleanup-script.shXXX".execute().text 94 | (new File(script)).write ''' 95 | ${bash_script} 96 | '''.trim() 97 | cmd = ['bash', '-x', script] 98 | println "SCRIPT:" 99 | println "\${new File(script).text}" 100 | println "COMMAND: \${cmd.join(' ')}" 101 | def sout = new StringBuilder(), serr = new StringBuilder() 102 | process = cmd.execute() 103 | process.waitFor() 104 | process.consumeProcessOutput(sout, serr) 105 | println "stdout> \\n\${sout}" 106 | println "stderr> \\n\${serr}" 107 | """.trim() 108 | 109 | //kill leftover junk threads to prevent thread leak; in case where postbuild groovy script is not configured 110 | (Thread.getAllStackTraces().keySet() as ArrayList).findAll { it.name.startsWith("agent-cleanup-") }.each { it.stop() } 111 | //execute cleanup script on remote agents in parallel 112 | threads = [] 113 | Jenkins.instance.slaves.findAll { it.computer.isOnline() }.each { slave -> 114 | threads << Thread.start { 115 | String result = """ 116 | |=================================== 117 | |SLAVE: ${slave.name} 118 | |DRYRUN: ${dryRun} 119 | |EXECUTED ON SLAVE: 120 | | ${RemotingDiagnostics.executeGroovy(groovy_script, slave.getChannel()).split('\n').join('\n ')} 121 | |=================================== 122 | """.stripMargin().trim() 123 | lock.withLock { 124 | out.println result 125 | } 126 | } 127 | //make threads easily searchable for cleanup purposes 128 | threads[-1].name = "agent-cleanup-${slave.name}-${threads[-1].id}" 129 | } 130 | live_threads = threads.findAll { it.isAlive() } 131 | interrupt = null 132 | while(live_threads) { 133 | lock.withLock { 134 | println "Waiting on ${live_threads.size()} threads:" 135 | println " ${live_threads*.name.join('\n ')}" 136 | //run a sleep but abort job if user aborts 137 | sleep(10000) { e -> 138 | assert e in InterruptedException 139 | interrupt = e 140 | } 141 | } 142 | if(interrupt) { 143 | throw interrupt 144 | } 145 | live_threads = threads.findAll { it.isAlive() } 146 | } 147 | threads.each { it.join() } 148 | println '===================================' 149 | println 'CLEANUP FINISHED RUNNING.' 150 | println '===================================' 151 | 152 | /* Here's a useful groovy postbuild step; necessary to prevent infinite thread growth 153 | List threads = (Thread.getAllStackTraces().keySet() as ArrayList).findAll { it.name.startsWith("agent-cleanup-") } 154 | if(threads) { 155 | manager.addWarningBadge("${threads.size()} thread(s) hard killed.") 156 | def summary = manager.createSummary("warning.gif") 157 | summary.appendText("

${threads.size()} thread(s) hard killed:

${threads*.name*.toString().join('\n')}
", false) 158 | threads.each { it.stop() } 159 | } 160 | */ 161 | -------------------------------------------------------------------------------- /delete-multibranch-pipeline-disabled-jobs.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /* 22 | Delete all abandoned branches and closed pull requests for multibranch pipeline jobs. 23 | 24 | Features: 25 | - Safest options defined by default to prevent accidental modification. 26 | - Dry run to see what would be deleted without modifying Jenkins. 27 | - Clean up all jobs in Jenkins or a specific job. 28 | - Optionally include deleting pull request jobs. 29 | - Can be a run from a Groovy job or from the Script console. 30 | - If run from a Groovy job, permissions are checked against the user 31 | building the job giving flexibility to expose this script in a self 32 | service manner. 33 | 34 | 35 | FreeStyle job named "_jervis_generator". 36 | */ 37 | 38 | import hudson.model.Cause.UserIdCause 39 | import hudson.model.Item 40 | import hudson.model.Job 41 | import hudson.model.ParametersAction 42 | import hudson.model.User 43 | import hudson.security.AccessDeniedException2 44 | import jenkins.model.Jenkins 45 | import org.jenkinsci.plugins.github_branch_source.PullRequestSCMHead 46 | import org.jenkinsci.plugins.workflow.multibranch.BranchJobProperty 47 | import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject 48 | 49 | 50 | /* 51 | User configurable options. If using a Groovy Job to execute this script, 52 | then don't bother customizing these values. Instead, add the following 53 | parameters to the job. 54 | 55 | - cleanup_all_jobs (boolean parameter) 56 | - job_full_name (string parameter) 57 | - include_pull_requests (boolean parameter) 58 | - dry_run (boolean parameter) 59 | */ 60 | 61 | 62 | // will delete disabled branches over all multibranch pipeline jobs in Jenkins 63 | boolean cleanupAllJobs = false 64 | 65 | // ignored if cleaning up all jobs 66 | String jobFullName = 'samrocketman/jervis-example-project' 67 | 68 | // removes disabled (closed) pull requests as well 69 | boolean includePullRequests = false 70 | 71 | // pretend to delete but don't actually delete, useful to see what would be deleted without modifying Jenkins 72 | boolean dryRun = true 73 | 74 | /* 75 | Helper Functions 76 | */ 77 | 78 | boolean hasDeletePermission(Item item) { 79 | item.hasPermission(Item.DELETE) 80 | } 81 | 82 | void message(String message) { 83 | if(isGroovyJob) { 84 | out?.println message 85 | } else { 86 | println message 87 | } 88 | } 89 | 90 | boolean isPullRequest(Job job) { 91 | BranchJobProperty prop 92 | if(job) { 93 | prop = job.getProperty(BranchJobProperty) 94 | } 95 | //check if the job is a pull request 96 | job && (prop?.branch?.head in PullRequestSCMHead) 97 | } 98 | 99 | void deleteDisabledJobs(WorkflowMultiBranchProject project, boolean includePullRequests = false, boolean dryRun = true) { 100 | project.items.findAll { Job j -> 101 | j.disabled && (includePullRequests || !isPullRequest(j)) 102 | }.each { Job j -> 103 | message "${(dryRun)? 'DRYRUN: ' : ''}Deleted ${project.fullName} ${isPullRequest(j)? 'pull request' : 'branch'} ${j.name} job." 104 | if(!dryRun) { 105 | j.delete() 106 | } 107 | } 108 | } 109 | 110 | def getJobParameter(String parameter, def defaultValue) { 111 | if(!isGroovyJob) { 112 | return defaultValue 113 | } 114 | def parameterValue = build?.actions.find { 115 | it in ParametersAction 116 | }?.parameters.find { 117 | it.name == parameter 118 | }?.value 119 | if((defaultValue in String) && (parameterValue in Boolean)) { 120 | 'false' != parameterValue 121 | } 122 | else { 123 | parameterValue.asType(defaultValue.getClass()) 124 | } 125 | } 126 | 127 | def tryImpersonate(Boolean groovyJob, Closure body) { 128 | if(groovyJob) { 129 | Jenkins.getInstance().ACL.as(User.get(build.getCause(UserIdCause.class).getUserId())).with { ctx -> 130 | try { 131 | body(ctx) 132 | } 133 | finally { 134 | ctx.close() 135 | } 136 | } 137 | } 138 | else { 139 | body(null) 140 | } 141 | } 142 | 143 | /* 144 | Main execution 145 | */ 146 | 147 | //bindings 148 | isGroovyJob = !(false in ['build', 'launcher', 'listener', 'out'].collect { binding.hasVariable(it) }) 149 | 150 | tryImpersonate(isGroovyJob) { 151 | if(isGroovyJob) { 152 | //authenticated as the user calling the build so appropriate permissions apply 153 | println "Impersonated ${Jenkins.instance.authentication.principal}" 154 | 155 | //get parameters from the groovy job 156 | cleanupAllJobs = getJobParameter('cleanup_all_jobs', cleanupAllJobs) 157 | jobFullName = getJobParameter('job_full_name', jobFullName) 158 | includePullRequests = getJobParameter('include_pull_requests', includePullRequests) 159 | dryRun = getJobParameter('dry_run', dryRun) 160 | } 161 | 162 | if(dryRun) { 163 | message 'NOTE: DRYRUN mode does not make any modifications to Jenkins.' 164 | } 165 | 166 | if(cleanupAllJobs) { 167 | message "NOTE: iterating across all multibranch pipelines in Jenkins to clean up branches${(includePullRequests)? ' and pull requests' : ''}." 168 | Jenkins.getInstance().getAllItems(WorkflowMultiBranchProject.class).findAll { WorkflowMultiBranchProject project -> 169 | hasDeletePermission(project) 170 | }.each { WorkflowMultiBranchProject project -> 171 | deleteDisabledJobs(project, includePullRequests, dryRun) 172 | } 173 | } 174 | else { 175 | message "NOTE: attempting to clean up specific job ${jobFullName} to clean up branches${(includePullRequests)? ' and pull requests' : ''}." 176 | if(jobFullName) { 177 | def project = Jenkins.get().getItemByFullName(jobFullName) 178 | if(!project || !(project in WorkflowMultiBranchProject)) { 179 | throw new RuntimeException('ERROR: Job is not a multibranch pipeline project. This script only works on multibranch pipelines.') 180 | } 181 | if(!hasDeletePermission(project)) { 182 | throw new AccessDeniedException2(Jenkins.get().authentication, Item.DELETE) 183 | } 184 | deleteDisabledJobs(project, includePullRequests, dryRun) 185 | } 186 | else { 187 | throw new RuntimeException('ERROR: Job full name not specified. There is nothing to clean up so this is an error.') 188 | } 189 | } 190 | } 191 | 192 | null 193 | -------------------------------------------------------------------------------- /audit-user-permissions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015-2024 Sam Gleske - https://github.com/samrocketman/jenkins-script-console-scripts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | /** 22 | This Jenkins script runs through accounts in Jenkins and highlights the 23 | permissions of each user. This script can be run from a Jenkins job via 24 | "Execute system Groovy script" or the script console. 25 | 26 | This script is useful for identifying users who might have more access than 27 | they need (web search "principle of least privilege"). This can also help 28 | identify users who should be deleted. 29 | 30 | Notes: 31 | - This script only reads user privileges and does not do anything destructive 32 | (i.e. it will not delete users). 33 | - This audit list only contains users which exist in Jenkins. 34 | - Accounts from security realm groups will have a Jenkins user created only when 35 | they log in the first time. Security realm users who have not logged in 36 | but would still get permissions are not counted here. 37 | - Jenkins automatically creates users from SCM authors in order to track 38 | their work across jobs and repositories. Therefore, it's possible accounts 39 | will exist in Jenkins which are not available in the security realm. These 40 | are not a concern. 41 | - Groovy bindings `out` and `build` are available in Jenkins jobs running via 42 | the "Execute system Groovy script" build step. It serves as a reliable 43 | means of detecting whether or not this script is run from the script 44 | console or a job. 45 | */ 46 | import hudson.model.Item 47 | import hudson.model.User 48 | import hudson.security.Permission 49 | import hudson.security.PermissionGroup 50 | import hudson.security.SidACL 51 | import hudson.tasks.Mailer 52 | import jenkins.model.Jenkins 53 | import org.acegisecurity.Authentication 54 | import org.acegisecurity.userdetails.UsernameNotFoundException 55 | 56 | if(!binding.hasVariable('writeToFile')) { 57 | writeToFile = false 58 | } 59 | if(!binding.hasVariable('includeCoreAccounts')) { 60 | includeCoreAccounts = true 61 | } 62 | if(writeToFile in String) { 63 | writeToFile = ('true' == writeToFile.toLowerCase()) 64 | } 65 | if(includeCoreAccounts in String) { 66 | includeCoreAccounts = ('true' == includeCoreAccounts.toLowerCase()) 67 | } 68 | if(!(writeToFile instanceof Boolean)) { 69 | throw new Exception('ERROR: writeToFile must be a boolean.') 70 | } 71 | if(!(includeCoreAccounts instanceof Boolean)) { 72 | throw new Exception('ERROR: includeCoreAccounts must be a boolean.') 73 | } 74 | 75 | /** 76 | This class simplifies getting details about a user and their access in a CSV 77 | format. Not only will it get permissions but it will smartly populate notes 78 | about a user based on known facts and issues. 79 | */ 80 | class UserCSV { 81 | private String id 82 | private String email 83 | private String fullName 84 | private Authentication impersonate 85 | private boolean coreAccount = false 86 | private boolean builtinAccount = false 87 | private static SidACL acl = Jenkins.instance.authorizationStrategy.rootACL 88 | private static List permissions = PermissionGroup.all*.permissions.flatten().findAll { Permission p -> 89 | !(this.displayPermission(p) in ['Overall:ConfigureUpdateCenter', 'Overall:RunScripts', 'Overall:UploadPlugins']) && 90 | !this.displayPermission(p).startsWith('N/A') 91 | } 92 | private static Permission admin = permissions.find { it.name == 'Administer' } 93 | private List notes = [] 94 | private String global_permissions = 'No permissions' 95 | private String item_permissions = 'No permissions' 96 | /** 97 | A new instance determines a user's access to Jenkins. 98 | */ 99 | def UserCSV(User u) { 100 | this.id = u.id ?: '' 101 | this.email = this.getEmail(u) 102 | this.fullName = u.fullName ?: '' 103 | switch(id) { 104 | case 'admin': 105 | this.notes << 'This built-in account is the first account created in the Jenkins 2.0 setup wizard when you set up Jenkins the first time.' 106 | this.notes << 'This account should be deleted because it is a default Jenkins account.' 107 | break 108 | case 'anonymous': 109 | this.notes << 'This core pseudo account is for users who are not authenticated with Jenkins a.k.a. all anonymous users.' 110 | break 111 | case 'authenticated': 112 | this.notes << 'This core pseudo account is for any user who has authenticated with Jenkins a.k.a. all logged in users.' 113 | break 114 | } 115 | this.coreAccount = (id in ['anonymous', 'authenticated']) as Boolean 116 | if(coreAccount) { 117 | this.notes << 'This account cannot be deleted.' 118 | } else { 119 | try { 120 | //for performance reasons, only try to impersonate a user once when this object is first instantiated 121 | this.impersonate = u.impersonate() 122 | } catch(UsernameNotFoundException e) { 123 | if(this.id != 'admin') { 124 | this.notes << 'This user does not exist in the security realm.' 125 | this.notes << 'It is an automatically created account from Jenkins tracking source code manager (SCM) authors.' 126 | this.notes << 'This is normal Jenkins behavior and can be ignored.' 127 | this.notes << 'It is safe to delete this account, but it will be recreated when the user shows up as an SCM author again.' 128 | } 129 | builtinAccount = true 130 | } 131 | } 132 | this.global_permissions = this.getGlobalPermissions() 133 | if(supportsItemPermissions()) { 134 | this.item_permissions = getItemPermissions() 135 | } 136 | if(this.global_permissions == 'No permissions' && this.item_permissions == 'No permissions' && !builtinAccount && !coreAccount) { 137 | this.notes << 'This user exists in the security realm and their account was created upon first login; however, they were not granted any permissions.' 138 | this.notes << 'This account should be deleted.' 139 | } 140 | if((this.global_permissions + this.item_permissions).contains('Job:Configure')) { 141 | this.notes << 'This user can elevate permissions of other users within jobs they can configure.' 142 | if(Jenkins.instance.numExecutors > 0) { 143 | this.notes << "This user can trivially elevate themselves to ${this.displayPermission(admin)} because jobs can be configured to run on the master." 144 | } 145 | } 146 | } 147 | private static Boolean supportsItemPermissions() { 148 | Jenkins.instance.authorizationStrategy.class.simpleName == 'ProjectMatrixAuthorizationStrategy' 149 | } 150 | private static String displayPermission(Permission p) { 151 | "${p.group.title}:${p.name}".toString() 152 | } 153 | private String getEmail(User u) { 154 | if(u.getProperty(Mailer.UserProperty)) { 155 | (u.getProperty(Mailer.UserProperty).address ?: '').toString() 156 | } else { 157 | '' 158 | } 159 | } 160 | private String getItemPermissions() { 161 | if(coreAccount || builtinAccount || this.global_permissions == 'Overall:Administer') { 162 | this.item_permissions 163 | } else { 164 | Set discovered_permissions = [] 165 | //find additional permissions granted in items e.g. folders, jobs, etc 166 | Jenkins.instance.getAllItems(Item.class).findAll { 167 | it.properties.find { it.class.simpleName == 'AuthorizationMatrixProperty' } 168 | }.each { Item item -> 169 | item.properties.find { 170 | it.class.simpleName == 'AuthorizationMatrixProperty' 171 | }.with { item_auth -> 172 | item_auth.getGrantedPermissions().keySet().findAll { Permission p -> 173 | !(displayPermission(p) in discovered_permissions) 174 | }.each { Permission p -> 175 | if(item_auth.acl.hasPermission(this.impersonate, p)) { 176 | discovered_permissions << displayPermission(p) 177 | } 178 | } 179 | } 180 | } 181 | discovered_permissions.sort().join(',') ?: this.item_permissions 182 | } 183 | } 184 | private String getGlobalPermissions() { 185 | if(coreAccount) { 186 | this.notes << 'Permissions cannot be determined on core accounts because they cannot be used as a normal user.' 187 | 'No permissions' 188 | } else if(builtinAccount) { 189 | 'No permissions' 190 | } else if(this.acl.hasPermission(this.impersonate, admin)) { 191 | this.notes << 'Admins can affect any infrastructure Jenkins integrates with including decrypting configured credentials.' 192 | displayPermission(admin) 193 | } else { 194 | permissions.findAll { Permission p -> 195 | p != admin && this.acl.hasPermission(this.impersonate, p) 196 | }.collect { 197 | displayPermission(it) 198 | }.join(',') ?: this.global_permissions 199 | } 200 | } 201 | public static String getCSVHeader() { 202 | List csvList = [ 203 | 'User', 204 | '"Full Name"', 205 | 'Email', 206 | '"Notes"', 207 | '"Global Permissions"' 208 | ] 209 | if(supportsItemPermissions()) { 210 | csvList << '"Additional permissions granted within folders or jobs"' 211 | } 212 | csvList.join(', ') 213 | } 214 | public String getCSV() { 215 | List csvList = [ 216 | this.id, 217 | "\"${this.fullName}\"", 218 | this.email, 219 | "\"${this.notes.join(' ')}\"", 220 | "\"${this.global_permissions}\"" 221 | ] 222 | if(supportsItemPermissions()) { 223 | csvList << "\"${this.item_permissions}\"" 224 | } 225 | csvList.join(', ') 226 | } 227 | } 228 | 229 | /** 230 | Print out the result to the script console or log a line to the job console 231 | output. It also optionally writes the line to a writer (used in other 232 | functions to write to a file). 233 | */ 234 | void writeLine(Writer writer = null, String line) { 235 | if(writer) { 236 | writer.write line + '\n' 237 | } 238 | if(binding.hasVariable('out')) { 239 | out.println line 240 | } else { 241 | println line 242 | } 243 | } 244 | 245 | /** 246 | optionalWriter is a Groovy binding which provides flexibility in executing a 247 | closure while writing output to a file, script console output, or the output 248 | to a Jenkins job. 249 | */ 250 | optionalWriter = { String file = null, Closure c -> 251 | if(file) { 252 | new File(file).withWriter('UTF-8') { writer -> 253 | c(writer) 254 | } 255 | } else { 256 | c(null as Writer) 257 | } 258 | } 259 | 260 | String separator = '-' * 80 261 | writeLine 'Start of CSV' 262 | writeLine separator 263 | String outputFile 264 | if(binding.hasVariable('out') && binding.hasVariable('build') && writeToFile) { 265 | outputFile = "${build.workspace}/audit-user-permissions.csv" 266 | } 267 | optionalWriter(outputFile) { writer -> 268 | writeLine(writer, UserCSV.getCSVHeader()) 269 | User.all.each { User u -> 270 | if(includeCoreAccounts || !(u.id in ['anonymous', 'authenticated'])) { 271 | writeLine(writer, new UserCSV(u).getCSV()) 272 | } 273 | } 274 | } 275 | writeLine separator 276 | writeLine 'End of CSV' 277 | --------------------------------------------------------------------------------