我已经设置了一些文件夹(使用Cloudbees文件夹插件)。
这听起来像是能够告诉 Jenkins 的最简单的命令:在文件夹 X 中构建每个作业。
我不想手动创建文件夹中每个作业的逗号分隔列表。 每当我想将作业添加到此文件夹时,我都不想添加到此列表中。 我只是希望它在运行时找到文件夹中的所有作业,并尝试构建它们。
我没有找到可以让我这样做的插件。
我尝试使用构建管道插件、批量构建器插件、多作业插件和其他一些插件。 似乎没有人支持我所追求的用例。我只想构建文件夹中的任何作业。换句话说,向此生成添加作业就像在此文件夹中创建作业一样简单。
我怎样才能做到这一点?
我已经使用 Jenkins 好几年了,但我还没有找到一种方法来做你所追求的事情。
我管理的最好的是:我有一个"运行每个作业"作业(其中包含您想要的所有作业的逗号分隔列表)。然后,我有一个单独的作业,该作业定期运行,并随着新项目的来来去去更新"运行每个作业"作业。
一种方法是创建一个运行 Groovy 脚本的管道作业,以枚举当前文件夹中的所有作业,然后启动它们。
下面的版本要求禁用沙盒(以便它可以访问Jenkins.instance
)。
def names = jobNames()
for (i = 0; i < names.size(); i++) {
build job: names[i], wait: false
}
@NonCPS
def jobNames() {
def project = Jenkins.instance.getItemByFullName(currentBuild.fullProjectName)
def childItems = project.parent.items
def targets = []
for (i = 0; i < childItems.size(); i++) {
def childItem = childItems[i]
if (!childItem instanceof AbstractProject) continue;
if (childItem.fullName == project.fullName) continue;
targets.add(childItem.fullName)
}
return targets
}
如果您使用管道库,那么以下内容会更好(并且不需要您允许 Groovy 沙盒转义:
将以下内容添加到您的库中:
package myorg;
public String runAllSiblings(jobName) {
def names = siblingProjects(jobName)
for (def i = 0; i < names.size(); i++) {
build job: names[i], wait: false
}
}
@NonCPS
private List siblingProjects(jobName) {
def project = Jenkins.instance.getItemByFullName(jobName)
def childItems = project.parent.items
def targets = []
for (def i = 0; i < childItems.size(); i++) {
def childItem = childItems[i]
if (!childItem instanceof AbstractProject) continue;
if (childItem.fullName == jobName) continue;
targets.add(childItem.fullName)
}
return targets
}
然后使用以下代码创建管道:
(new myorg.JobUtil()).runAllSiblings(currentBuild.fullProjectName)
是的,有一些方法可以进一步简化这一点,但它应该给你一些想法。
我开发了一个Groovy脚本来做到这一点。 它工作得很好。 有两个作业,initBuildAll,它运行时髦的脚本,然后启动"buildAllJobs"作业。 在我的设置中,我每天启动 InitBuildAll 脚本。 你可以用另一种适合你的方式触发它。我们还没有满CI,所以每天对我们来说已经足够了。
需要注意的是:这些工作都是相互独立的。 如果这不是您的情况,则可能需要进行一些调整。
这些作业位于名为 MultiBuild 的单独文件夹中。 要生成的作业位于名为"项目"的文件夹中。
import com.cloudbees.hudson.plugins.folder.Folder
import javax.xml.transform.stream.StreamSource
import hudson.model.AbstractItem
import hudson.XmlFile
import jenkins.model.Jenkins
Folder findFolder(String folderName) {
for (folder in Jenkins.instance.items) {
if (folder.name == folderName) {
return folder
}
}
return null
}
AbstractItem findItem(Folder folder, String itemName) {
for (item in folder.items) {
if (item.name == itemName) {
return item
}
}
null
}
AbstractItem findItem(String folderName, String itemName) {
Folder folder = findFolder(folderName)
folder ? findItem(folder, itemName) : null
}
String listProjectItems() {
Folder projectFolder = findFolder('Projects')
StringBuilder b = new StringBuilder()
if (projectFolder) {
for (job in projectFolder.items.sort{it.name.toUpperCase()}) {
b.append(',').append(job.fullName)
}
return b.substring(1) // dump the initial comma
}
return b.toString()
}
File backupConfig(XmlFile config) {
File backup = new File("${config.file.absolutePath}.bak")
FileWriter fw = new FileWriter(backup)
config.writeRawTo(fw)
fw.close()
backup
}
boolean updateMultiBuildXmlConfigFile() {
AbstractItem buildItemsJob = findItem('MultiBuild', 'buildAllProjects')
XmlFile oldConfig = buildItemsJob.getConfigFile()
String latestProjectItems = listProjectItems()
String oldXml = oldConfig.asString()
String newXml = oldXml;
println latestProjectItems
println oldXml
def mat = newXml =~ '\<projects\>(.*)\<\/projects\>'
if (mat){
println mat.group(1)
if (mat.group(1) == latestProjectItems) {
println 'no Change'
return false;
} else {
// there's a change
File backup = backupConfig(oldConfig)
def newProjects = "<projects>${latestProjectItems}</projects>"
newXml = mat.replaceFirst(newProjects)
XmlFile newConfig = new XmlFile(oldConfig.file)
FileWriter nw = new FileWriter(newConfig.file)
nw.write(newXml)
nw.close()
println newXml
println 'file updated'
return true
}
}
false
}
void reloadMultiBuildConfig() {
AbstractItem job = findItem('MultiBuild', 'buildAllProjects')
def configXMLFile = job.getConfigFile();
def file = configXMLFile.getFile();
InputStream is = new FileInputStream(file);
job.updateByXml(new StreamSource(is));
job.save();
println "MultiBuild Job updated"
}
if (updateMultiBuildXmlConfigFile()) {
reloadMultiBuildConfig()
}
Wayne Booth的"运行每一项工作"方法的略有不同。 经过一番挠头,我能够以作业DSL格式定义"运行每个作业"。
优点是我可以在版本控制中维护我的作业配置。 例如
job('myfolder/build-all'){
publishers {
downstream('myfolder/job1')
downstream('myfolder/job2')
downstream('myfolder/job2')
}
}
管道作业
作为管道作业运行时,可以使用以下内容:
echo jobNames.join('n')
jobNames.each {
build job: it, wait: false
}
@NonCPS
def getJobNames() {
def project = Jenkins.instance.getItemByFullName(currentBuild.fullProjectName)
project.parent.items.findAll {
it.fullName != project.fullName && it instanceof hudson.model.Job
}.collect { it.fullName }
}
脚本控制台
可以从脚本控制台使用以下代码片段来计划某个文件夹中的所有作业:
import hudson.model.AbstractProject
Jenkins.instance.getAllItems(AbstractProject.class).each {
if(it.fullName =~ 'path/to/folder') {
(it as AbstractProject).scheduleBuild2(0)
}
}
通过一些修改,您将能够创建一个 jenkins 共享库方法(需要在沙箱之外运行并且需要 @NonCPS
),例如:
import hudson.model.AbstractProject
@NonCPS
def triggerItemsInFolder(String folderPath) {
Jenkins.instance.getAllItems(AbstractProject.class).each {
if(it.fullName =~ folderPath) {
(it as AbstractProject).scheduleBuild2(0)
}
}
}
引用管道脚本来运行父作业,该父作业将触发 @WayneBooth 建议的其他作业
pipeline {
agent any
stages {
stage('Parallel Stage') {
parallel {
stage('Parallel 1') {
steps {
build(job: "jenkins_job_1")
}
}
stage('Parallel 2') {
steps {
build(job: "jenkins_job_2")
}
}
}
}
}
运行此类临时命令的最佳方法是使用脚本控制台(可以在"管理 Jenkins"下找到)。
控制台允许运行 Groovy 脚本 - 脚本控制 Jenkins 功能。该文档可以在 Jenkins JavaDoc 下找到。
一个简单的脚本,可立即触发给定文件夹结构下的所有多分支管道项目(在本例中folder/subfolder/projectName
):
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject
import hudson.model.Cause.UserIdCause
Jenkins.instance.getAllItems(WorkflowMultiBranchProject.class).findAll {
return it.fullName =~ '^folder/subfolder/'
}.each {
it.scheduleBuild(0, new UserIdCause())
}
该脚本针对 Jenkins 2.324 进行了测试。