如何修改artifactCleanup插件以保留仓库各目录的最后10个制品
需求:修改ArtifactCleanup插件实现按目录保留最新10个制品
我找到了artifactCleanup插件,已部署到Jenkins服务器,现在需要修改它的逻辑:原插件会清理过去X个月未被下载的所有制品,我希望改为在仓库及每个子目录中仅保留最后10个制品。
示例
原仓库结构
Main_Repo -folder1(含15个制品) -folder2(含20个制品) -art1 -art2 -... -art25
修改后需保留的结构
Main_Repo -folder1(含10个制品) -folder2(含10个制品) -art1 -art2 -... -art10
请问这是否可行?
当前插件主函数代码
private def artifactCleanup(String timeUnit, int timeInterval, String[] repos, log, paceTimeMS, dryRun = false, disablePropertiesSupport = false) { log.info "Starting artifact cleanup for repositories $repos, until $timeInterval ${timeUnit}s ago with pacing interval $paceTimeMS ms, dryrun: $dryRun, disablePropertiesSupport: $disablePropertiesSupport" cancelOnNullRepos(repos,'repos parameter must be specified before initiating cleanup.') // Create Map(repo, paths) of skiped paths (or others properties supported in future ...) def skip = [:] if ( ! disablePropertiesSupport && repos){ skip = getSkippedPaths(repos) } def calendarUntil = Calendar.getInstance() calendarUntil.add(mapTimeUnitToCalendar(timeUnit), -timeInterval) def calendarUntilFormatted = new SimpleDateFormat("yyyy/MM/dd HH:mm").format(calendarUntil.getTime()); log.info "Removing all artifacts not downloaded since $calendarUntilFormatted" Global.stopCleaning = false int cntFoundArtifacts = 0 int cntNoDeletePermissions = 0 long bytesFound = 0 long bytesFoundWithNoDeletePermission = 0 def artifactsCleanedUp = searches.artifactsNotDownloadedSince(calendarUntil, calendarUntil, repos) artifactsCleanedUp.find { try { while ( Global.pauseCleaning ) { log.info "Pausing by request" sleep( 60000 ) } if ( Global.stopCleaning ) { log.info "Stopping by request, ending loop" return true } if ( ! disablePropertiesSupport && skip[ it.repoKey ] && StringUtils.startsWithAny(it.path, skip[ it.repoKey ])){ if (log.isDebugEnabled()){ log.debug "Skip $it" } return false } bytesFound += repositories.getItemInfo(it)?.getSize() cntFoundArtifacts++ if (!security.canDelete(it)) { bytesFoundWithNoDeletePermission += repositories.getItemInfo(it)?.getSize() cntNoDeletePermissions++ } if (dryRun) { log.info "Found $it " log.info "\t==> currentUser: ${security.currentUser().getUsername()}" log.info "\t==> canDelete: ${security.canDelete(it)}" } else { if (security.canDelete(it)) { log.info "Deleting $it" repositories.delete it log.info "Deleted..." } else { log.info "Can't delete $it (user ${security.currentUser().getUsername()} has no delete permissions), " } } } catch (ItemNotFoundRuntimeException ex) { log.info "Failed to find $it, skipping" } def sleepTime = (Global.paceTimeMS > 0) ? Global.paceTimeMS : paceTimeMS if (sleepTime > 0) { sleep( sleepTime ) } return false } if (dryRun) { log.info "Dry run - nothing deleted. Found $cntFoundArtifacts artifacts consuming $bytesFound bytes" if (cntNoDeletePermissions > 0) { log.info "$cntNoDeletePermissions artifacts cannot be deleted due to lack of permissions ($bytesFoundWithNoDeletePermission bytes)" } } else { log.info "Finished cleanup, deleting $cntFoundArtifacts artifacts that took up $bytesFound bytes" if (cntNoDeletePermissions > 0) { log.info "$cntNoDeletePermissions artifacts could not be deleted due to lack of permissions ($bytesFoundWithNoDeletePermission bytes)" } } }
疑问点
我不确定如何找到searches对象,以及它调用的searches.artifactsNotDownloadedSince(calendarUntil, calendarUntil, repos)方法,希望得到帮助。
解答
可行性结论
这个需求完全可行,但需要替换原插件的核心搜索逻辑,不再基于下载时间筛选,而是按目录分组后保留最新的10个制品。
关于searches对象的说明
searches是Artifactory用户插件的内置上下文对象,属于Artifactory插件API的一部分,提供制品搜索相关的方法。原方法artifactsNotDownloadedSince是按下载时间范围筛选制品,但我们不需要这个逻辑,需要替换成遍历目录、分组筛选的逻辑。
修改思路
- 遍历目标仓库的所有目录(包括根目录)
- 对每个目录下的制品,按最后修改时间排序(最新的在前,可根据需求替换为创建时间)
- 保留排序后的前10个,删除其余制品
- 复用原插件的权限检查、dry-run、跳过路径等逻辑
核心代码修改示例
替换原插件中def artifactsCleanedUp = searches.artifactsNotDownloadedSince(...)及后续的遍历逻辑,改为以下逻辑:
// 初始化统计变量(和原插件保持一致) Global.stopCleaning = false int cntFoundArtifacts = 0 int cntNoDeletePermissions = 0 long bytesFound = 0 long bytesFoundWithNoDeletePermission = 0 // 遍历目标仓库 repos.each { repoKey -> // 获取仓库根目录下的所有子项(目录和制品) def rootItems = repositories.getChildren(repoKey, "") processDirectory(repoKey, "", rootItems, log, dryRun, skip, security, paceTimeMS) } // 递归处理目录的方法 private def processDirectory(String repoKey, String currentPath, def items, log, dryRun, skip, security, paceTimeMS) { def artifacts = [] def directories = [] // 分离当前目录下的制品和子目录 items.each { item -> if (item.isFolder()) { directories.add(item) } else { artifacts.add(item) } } // 处理当前目录下的制品:排序后保留最新10个 if (artifacts.size() > 10) { // 按最后修改时间降序排序 def sortedArtifacts = artifacts.sort { -it.lastModified } // 要删除的是第11个及以后的制品 def artifactsToDelete = sortedArtifacts.drop(10) artifactsToDelete.each { artifact -> def itemPath = "${currentPath}/${artifact.name}".replaceAll("//", "/") def itemId = "${repoKey}:${itemPath}" // 跳过配置中指定的路径 if (skip[repoKey] && StringUtils.startsWithAny(itemPath, skip[repoKey])) { if (log.isDebugEnabled()) { log.debug "Skip $itemId" } return } try { while (Global.pauseCleaning) { log.info "Pausing by request" sleep(60000) } if (Global.stopCleaning) { log.info "Stopping by request, ending loop" return true } def itemInfo = repositories.getItemInfo(itemId) long size = itemInfo?.getSize() ?: 0 if (!security.canDelete(itemId)) { log.info "Can't delete $itemId (user ${security.currentUser().getUsername()} has no delete permissions)" cntNoDeletePermissions++ bytesFoundWithNoDeletePermission += size return } cntFoundArtifacts++ bytesFound += size if (dryRun) { log.info "Found artifact to delete: $itemId" log.info "\t==> canDelete: ${security.canDelete(itemId)}" } else { log.info "Deleting $itemId" repositories.delete(itemId) log.info "Deleted..." } def sleepTime = (Global.paceTimeMS > 0) ? Global.paceTimeMS : paceTimeMS if (sleepTime > 0) { sleep(sleepTime) } } catch (ItemNotFoundRuntimeException ex) { log.info "Failed to find $itemId, skipping" } } } // 递归处理子目录 directories.each { dir -> def newPath = "${currentPath}/${dir.name}".replaceAll("//", "/") def dirItems = repositories.getChildren(repoKey, newPath) processDirectory(repoKey, newPath, dirItems, log, dryRun, skip, security, paceTimeMS) } }
注意事项
- 确保统计变量
cntFoundArtifacts等在主函数开头正确初始化 - 可根据需求调整排序逻辑(比如用
creationDate代替lastModified) - 测试前务必开启
dryRun模式,确认要删除的制品列表符合预期 - 确保插件运行账号有足够的遍历和删除权限
内容的提问来源于stack exchange,提问作者Elydasian
相关产品推荐
相关产品推荐

