如何延迟jenkins工作直到达到一定时间?

在我们当前的项目设置中,我们有工作A,B,C.

成功构建A,B或C时,我们希望将生成的工件部署到开发服务器.因此我们使用工作D.

由于部署会重新创建开发数据库,​​因此我们只希望每隔一小时运行一次该作业.我们的测试人员熟悉这个计划并以这种方式工作.

实际上当D由A,B或C触发时,会启动一个脚本,等待服务器分钟为00.如果其中一个触发器作业在等待时再次触发D,则以前等待的脚本将被取消并重新启动.如果脚本达到小时的00,则发生部署.

主要问题是,在最坏的情况下,作业D会阻塞建筑物槽59分钟.

默认情况下,每小时运行一次作业不是一种选择,因为即使没有任何更改,也会发生部署.

在更改后运行作业也很糟糕,因为测试人员习惯于每小时部署.

我知道有一个’安静时间’选项,但这只能让我设置一个相对于触发时间的等待时间.我需要的是一个“安静的时间”,将工作推迟到一定时间.
有人就如何实现这一目标提出建议吗?

最佳答案 我开发了以下解决方案:

1)在作业A,B和C中使用Conditional BuildStep Plugin在作业D的config.xml中设置cron计划:

>构建

>条件步骤(单个)

>运行?:执行shell [仅用于测试,在此使用您的构建步骤]

>命令

#!/bin/bash
echo "  This script is a placeholder to represent a build step that can succeed or not succeed"
true   # to test build success → Groovy script should be executed
#false  # to test not successful build → nothing should be done
 

> Builder:执行系统Groovy脚本

>◉Groovy命令

// From: How to delay Jenkins job until a certain time was reached?
//       https://stackoverflow.com/questions/27952216/1744774

// -----------------------------------------------------------
// Adapt these according to your environment
final String DOWNSTREAM_NAME = 'SO-27952216-Downstream-job'
final String CRON_SCHEDULE = '0 * * * *'
// -----------------------------------------------------------

final String SEPARATOR = new String(new char[8]).replace('\0', '-')
println("  ${SEPARATOR} Adapting configuration of ${DOWNSTREAM_NAME} ${SEPARATOR}")

import jenkins.model.*
import hudson.model.*

final Project DOWNSTREAM_JOB = Jenkins.instance.getItem(DOWNSTREAM_NAME)
final String DOWNSTREAM_CONFIG = DOWNSTREAM_JOB.getRootDir().getPath() + "/config.xml"  

//import hudson.triggers.*
//DOWNSTREAM_JOB.getTrigger(TimerTrigger.class).spec = CRON_SCHEDULE
// leads to:
//   ERROR: Build step failed with exception groovy.lang.ReadOnlyPropertyException: 
//       Cannot set readonly property: spec for class: hudson.triggers.TimerTrigger

import org.w3c.dom.*
import javax.xml.parsers.*
import javax.xml.xpath.*

println("  Reading ${DOWNSTREAM_CONFIG}")
Document doc = 
    DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(DOWNSTREAM_CONFIG)
XPathExpression expr = XPathFactory.newInstance().newXPath()
    .compile("/project/triggers/hudson.triggers.TimerTrigger/spec");
final org.w3c.dom.Node SCHEDULE_NODE = expr.evaluate(doc, XPathConstants.NODE)

println(String.format(
    "  Changing Build Triggers → Build periodically → Schedule from '%s' to ' %s'",
    SCHEDULE_NODE.getTextContent(), CRON_SCHEDULE))
SCHEDULE_NODE.setTextContent(CRON_SCHEDULE)

import javax.xml.transform.*
import javax.xml.transform.dom.*
import javax.xml.transform.stream.*

println("  Writing ${DOWNSTREAM_CONFIG}")
Transformer transformer = TransformerFactory.newInstance().newTransformer()
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
transformer.transform(new DOMSource(doc), new StreamResult(new File(DOWNSTREAM_CONFIG)))

println("  ${SEPARATOR} Adapted configuration of ${DOWNSTREAM_NAME} ${SEPARATOR}")
 

2)在作业D中使用构建后操作→Groovy Postbuild重置其config.xml中的cron计划:

>构建

>执行shell [仅用于测试,在此处使用您的部署步骤]

>命令

#!/bin/bash
echo "  This script is a placeholder to represent a build step"
 

>后期制作行动

> Groovy Postbuild

> Groovy脚本

// From: How to delay Jenkins job until a certain time was reached?
//       https://stackoverflow.com/questions/27952216/1744774

// -----------------------------------------------------------
// Adapt these according to your environment
final String THIS_JOB_NAME = 'SO-27952216-Downstream-job'
final String CRON_SCHEDULE = ''
// -----------------------------------------------------------

final Object LOG = manager.listener.logger
final String SEPARATOR = new String(new char[8]).replace('\0', '-')
LOG.println("  ${SEPARATOR} Adapting configuration of ${THIS_JOB_NAME} ${SEPARATOR}")

import jenkins.model.*
import hudson.model.*

final Project THIS_JOB = Jenkins.instance.getItem(THIS_JOB_NAME)
final String THIS_JOB_CONFIG = THIS_JOB.getRootDir().getPath() + "/config.xml"  

//import hudson.triggers.*
//THIS_JOB.getTrigger(TimerTrigger.class).spec = CRON_SCHEDULE
// leads to:
//   ERROR: Build step failed with exception groovy.lang.ReadOnlyPropertyException: 
//       Cannot set readonly property: spec for class: hudson.triggers.TimerTrigger

import org.w3c.dom.*;
import javax.xml.parsers.*
import javax.xml.xpath.*

LOG.println("  Reading ${THIS_JOB_CONFIG}")
Document doc = 
    DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(THIS_JOB_CONFIG)
XPathExpression expr = XPathFactory.newInstance().newXPath()
    .compile("/project/triggers/hudson.triggers.TimerTrigger/spec")
final org.w3c.dom.Node SCHEDULE_NODE = expr.evaluate(doc, XPathConstants.NODE)

LOG.println(String.format(
    "  Changing Build Triggers → Build periodically → Schedule from '%s' to ' %s'",
    SCHEDULE_NODE.getTextContent(), CRON_SCHEDULE))
SCHEDULE_NODE.setTextContent(CRON_SCHEDULE)

import javax.xml.transform.*
import javax.xml.transform.dom.*
import javax.xml.transform.stream.*

LOG.println("  Writing ${THIS_JOB_CONFIG}")
Transformer transformer = TransformerFactory.newInstance().newTransformer()
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
transformer.transform(new DOMSource(doc), new StreamResult(new File(THIS_JOB_CONFIG)))

LOG.println("  ${SEPARATOR} Adapted configuration of ${THIS_JOB_NAME} ${SEPARATOR}")
 

欢迎提示为什么println()在此Post-build Action中不会向Jenkin的控制台输出打印任何内容.

更新:知道了!根据[JENKINS-18651 – Enable correct writing to the job’s log from a post build script],它是manager.listener.logger.println()而不仅仅是println().

应该注意的是,这种解决方案有三种情况会导致问题:

>作业A,B,C或D写入config.xml,用户同时通过UI保存D的配置→并发写访问.

>如果UI首先打开config.xml,我们可以通过异常处理和在脚本中几秒钟后重试来覆盖它.
>如果我们的脚本是第一个打开config.xml我们运气不好→UI可能会在“恶魔”Jenkins的制度下显示错误:
.

>作业A或B或C在与D同时写入config.xml,反之亦然→我们可以通过异常处理和在我们的脚本中几秒钟后重试来覆盖它.
>作业A,B,C或D写入config.xml,Jenkins’TimerTrigger同时读取文件(它每分钟执行一次)→由于写入尚未完全读取而读取错误数据.

我将其作为读者的挑战,以避免在多个上游作业的情况下通过以下方式重复代码:

>将上游作业的Groovy代码移动到:
HTTP://jenkins/userContent/scripts/JobCronScheduleConfigurator.groovy
>在任何上游作业中使用以下内容:

>构建

>条件步骤(单个)

>生成器

>◉Groovy脚本文件
HTTP://jenkins/userContent/scripts/JobCronScheduleConfigurator.groovy

不幸的是,构建后的操作→Groovy Postbuild不支持Groovy脚本文件,所以在这方面仍然存在代码重复(并且记录,参见上文,无论如何都是不同的).

点赞