当terraform.yaml无部署项时,如何删除Jenkins生成的种子作业?
解决方案:当terraform.yaml的deployments为空时删除对应Jenkins种子作业
问题分析
当前的CreateTerraformDeployJobs仅在deployments有内容时生成作业DSL脚本,jobDsl步骤只会对比当前DSL与历史生成的作业定义来删除不再存在的作业。但当deployments为空时,DSL脚本为空,无法触发自动删除逻辑,导致该代码库生成的子作业残留。
我们需要主动识别并删除当前代码库对应的所有Jenkins作业,以下是两种可行的实现方式:
方法一:修改CreateTerraformDeployJobs函数,内置空场景处理
直接在原函数中加入判断,当deployments为空时,通过Jenkins API筛选并删除目标作业:
修改后的完整代码:
/* 基于terraform.yaml内容创建Terraform部署任务 */ def call(Map terraformConfig, String repoPath, Map inlineParams = [:]) { dslScripts = [] deployments = terraformConfig["deployments"] latestVersion = terraformConfig["version"] if (deployments) { // 原有生成作业的逻辑保持不变 deployments.each { deployment -> deployment["environments"].each { environment, setting -> if (deployment.subcomponent) { deploymentPath = "${deployment.namespace}/${deployment.application}/${deployment.component}-${deployment.subcomponent}-${environment}" } else { deploymentPath = "${deployment.namespace}/${deployment.application}/${deployment.component}-${environment}" deployment.subcomponent = "" } if (setting.approvers) { approvers = setting.approvers.join(',') } else { approvers = "authenticated" } repo_list_for_tfvars_param = [ "neoload-infra", "devops-ec2-infra" ] repo_name = repoPath.replace("DET/", "").toString().trim() repo_exists = repo_list_for_tfvars_param.contains(repo_name) def jobScript if (repo_exists) { jobScript = """ pipelineJob("${deploymentPath}") { description("Generated for repoPath: ${repoPath}") parameters { stringParam('Version', 'master', '要部署的基础设施版本(例如 0.1.0, master, dev, feature/foobar 等)') stringParam('ExtraArgs', '', '额外的Terraform参数,例如 \"-parallelism=1\" 或 \"-target=random_id.managed_8bytes\"。适用于所有阶段:plan/apply/destroy') stringParam('tfVars', '', "Terraform变量,例如 -var='instance_count=3' -var='instance_type=t3.small'。变量必须在Terraform代码中定义,且适用于所有阶段:plan/apply/destroy") choiceParam('DebugLogLevel', ['disabled', 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'], 'Terraform调试日志级别。适用于所有阶段:plan/apply/destroy') stringParam('Destroy', '', '要销毁此环境,请在此文本框中输入 \"Please destroy all resources\" 或 \"Please destroy all resources with force\"。第一种选项流程:plan -destroy => apply;第二种:plan => destroy。') stringParam('AutoApprove', '', '要自动批准构建或销毁此环境,请在此文本框中输入 \"Please run with auto approve\"。') } environmentVariables { env('namespace', '${deployment.namespace}') env('application', '${deployment.application}') env('environment', '${environment}') env('component', '${deployment.component}') env('subcomponent', '${deployment.subcomponent}') env('aws_account_id', '${setting.aws_account_id}') env('aws_region', '${setting.aws_region}') env('deploymentPath', '${deploymentPath}') env('buildJobUrl', '${JOB_URL}') env('repoPath', '${repoPath}') env('WEBEX_SPACE_ID', '${WEBEX_SPACE_ID}') env('approvers', '${approvers}') } logRotator { numToKeep(50) } definition { cps { script(""" @Library(value='tls-lib@master', changelog=false) _ TerraformDeployPipeline(params, ${inlineParams}) """.stripIndent()) sandbox(true) } } } """.stripIndent() } else { jobScript = """ pipelineJob("${deploymentPath}") { description("Generated for repoPath: ${repoPath}") parameters { stringParam('Version', 'master', '要部署的基础设施版本(例如 0.1.0, master, dev, feature/foobar 等)') stringParam('ExtraArgs', '', '额外的Terraform参数,例如 \"-parallelism=1\" 或 \"-target=random_id.managed_8bytes\"。适用于所有阶段:plan/apply/destroy') choiceParam('DebugLogLevel', ['disabled', 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'], 'Terraform调试日志级别。适用于所有阶段:plan/apply/destroy') stringParam('Destroy', '', '要销毁此环境,请在此文本框中输入 \"Please destroy all resources\" 或 \"Please destroy all resources with force\"。第一种选项流程:plan -destroy => apply;第二种:plan => destroy。') stringParam('AutoApprove', '', '要自动批准构建或销毁此环境,请在此文本框中输入 \"Please run with auto approve\"。') } environmentVariables { env('namespace', '${deployment.namespace}') env('application', '${deployment.application}') env('environment', '${environment}') env('component', '${deployment.component}') env('subcomponent', '${deployment.subcomponent}') env('aws_account_id', '${setting.aws_account_id}') env('aws_region', '${setting.aws_region}') env('deploymentPath', '${deploymentPath}') env('buildJobUrl', '${JOB_URL}') env('repoPath', '${repoPath}') env('WEBEX_SPACE_ID', '${WEBEX_SPACE_ID}') env('approvers', '${approvers}') } logRotator { numToKeep(50) } definition { cps { script(""" @Library(value='tls-lib@master', changelog=false) _ TerraformDeployPipeline(params, ${inlineParams}) """.stripIndent()) sandbox(true) } } } """.stripIndent() } dslScripts.add(jobScript) } } } else { // 当deployments为空时,生成删除该repoPath对应作业的脚本 def deleteScript = """ import jenkins.model.Jenkins import hudson.model.AbstractProject def jenkinsInstance = Jenkins.get() def targetRepoPath = "${repoPath}" def jobsToDelete = [] // 遍历所有作业,筛选出当前代码库生成的作业 jenkinsInstance.getAllItems(AbstractProject.class).each { job -> if (job.description?.contains("Generated for repoPath: \${targetRepoPath}")) { jobsToDelete.add(job.fullName) } } // 执行删除操作 jobsToDelete.each { jobFullName -> def job = jenkinsInstance.getItemByFullName(jobFullName) if (job) { println "Deleting job: \${jobFullName}" job.delete() } } """.stripIndent() dslScripts.add(deleteScript) } jobDsl( scriptText: dslScripts.join('\n'), removedJobAction: 'DELETE', removedViewAction: 'DELETE', lookupStrategy: 'JENKINS_ROOT' ) }
关键修改点
- 作业标记:在生成每个作业时添加
description字段,明确标记该作业所属的代码库repoPath,确保后续删除时能精准识别。 - 空场景处理:当
deployments为空时,生成Groovy脚本通过Jenkins API遍历所有作业,筛选出带有对应标记的作业并执行删除。 - 权限要求:确保执行流水线的账号拥有
Job/Delete权限,否则无法完成删除操作。
方法二:在构建流水线中单独处理删除逻辑
如果不想修改CreateTerraformDeployJobs函数,可以在构建流水线的Seed阶段加入判断,直接调用独立的删除函数:
修改构建流水线的Seed阶段
stage ("Seed") { when { branch 'master' } environment { repoPath = "${GIT_URL.split(':')[1].replace('.git', '')}" } steps { script { if (terraformConfig.deployments) { CreateTerraformDeployJobs(terraformConfig, repoPath, params) } else { deleteRepoGeneratedJobs(repoPath) } } } }
添加删除函数deleteRepoGeneratedJobs
def deleteRepoGeneratedJobs(String repoPath) { script { def jenkins = Jenkins.get() def targetRepo = repoPath def jobsToDelete = [] jenkins.getAllItems(AbstractProject.class).each { job -> // 通过作业的description识别目标作业 if (job.description?.contains("Generated for repoPath: ${targetRepo}")) { jobsToDelete.add(job.fullName) } } if (jobsToDelete.isEmpty()) { println "未找到属于代码库 ${targetRepo} 的作业,无需删除" } else { println "将删除 ${jobsToDelete.size()} 个作业:" jobsToDelete.each { jobName -> def job = jenkins.getItemByFullName(jobName) if (job) { println "删除作业:${jobName}" job.delete() } else { println "作业 ${jobName} 不存在,跳过" } } } } }
注意事项
- 权限配置:确保Jenkins允许流水线脚本执行Jenkins API操作,并且执行账号拥有
Job/Delete和Script Approval权限。 - 标记可靠性:使用
description标记作业比依赖环境变量更稳定,避免环境变量被覆盖或丢失导致识别失败。 - 测试验证:在生产环境部署前,先在测试环境验证删除逻辑,防止误删无关作业。
内容的提问来源于stack exchange,提问作者hashim vayalar
相关产品推荐
相关产品推荐

