使用每天自动运行的 groovy 脚本,我能够做到这一点。
该脚本设置在 Admin - Execute script 的任务中,在 nexus 较新版本中默认禁用,我在常见问题部分中的 Scripting Nexus Repository Manager 3 以及 How to Determine the Location of the Nexus 3 Data Directory 解决了这个问题。
该脚本基于来自不同地方的文档、问题和代码(例如: StorageTxImpl.java 是您可以找到获取/删除资产、组件等的方法的地方)。它也受到这些启发
Using the Nexus3 API how do I get a list of artifacts in a repository、NEXUS-14837 和 Nexus 3 Groovy Script development environment setup
脚本:
脚本必须在第二个任务之前运行(即等于第一个,之前或之后都没有关系)。这些策略也不再需要,因此不再分配给存储库。
它是如何工作或做什么的:
- 获取存储库
- 获取 repo 的组件
- 按名称对它们进行分组(例如: repository/my-repository/some-project/service-A)
- 为每个服务循环其组件并获取其资产
- 按资产的
last_downloaded 过滤资产,并仅保留与最近的 3 个不匹配的资产,例如
- 删除与资产相关的组件(nexus
deleteComponent(cp) 在内部删除资产及其 blob)
注意:我看到脚本可以参数化,但在我的情况下不需要
注意:这可以更新以循环所有存储库,但我只需要一个
import org.sonatype.nexus.repository.storage.Asset
import org.sonatype.nexus.repository.storage.Query
import org.sonatype.nexus.repository.storage.StorageFacet
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import org.sonatype.nexus.repository.Repository
class RepositoryProcessor {
private final log
private final repository
private final String repoName = 'my-repository'
private final String[] ignoreVersions = ['build-latest']
private final int processIfSizeGt = 3
private final int delAllButMostRecentNImages = 2
RepositoryProcessor(log, repository) {
this.log = log
this.repository = repository
}
void processRepository() {
def repo = repository.repositoryManager.get(repoName)
log.debug("found repository: {}", repo)
// will use default of sonatype
// https://github.com/sonatype/nexus-public/blob/master/components/nexus-repository/src/main/java/org/sonatype/nexus/repository/storage/StorageFacetImpl.java
StorageFacet storageFacet = repo.facet(StorageFacet)
log.debug("initiated storage facet: {}", storageFacet.toString())
// tx of type https://github.com/sonatype/nexus-public/blob/master/components/nexus-repository/src/main/java/org/sonatype/nexus/repository/storage/StorageTxImpl.java $$EnhancerByGuice ??
def transaction = storageFacet.txSupplier().get()
log.debug("initiated transaction instance: {}", transaction.toString())
try {
transaction.begin()
log.info("asset count {}", transaction.countAssets(Query.builder().build(), [repo]))
log.info("components count {}", transaction.countComponents(Query.builder().build(), [repo]))
// queried db is orientdb, syntax is adapted to it
def components = transaction.findComponents(Query.builder()
// .where("NOT (name LIKE '%service-A%')")
// .and("NOT (name LIKE '%service-B%')")
.build(), [repo])
// cp and cpt refers to component
// group by name eg: repository/my-repository/some-project/service-A
def groupedCps = components.groupBy{ it.name() }.collect()
// fetch assets for each cp
// and set them in maps to delete the old ones
groupedCps.each{ cpEntry ->
// process only if its greater than the minimum amount of images per service
if (cpEntry.value.size > processIfSizeGt) {
// single component processing (i.e this would be done for each service)
def cpMap = [:] // map with key eq id
def cpAssetsMap = [:] // map of cp assets where key eq cp id
// process service cpts
cpEntry.value.each { cp ->
// cp id of type https://github.com/sonatype/nexus-public/blob/master/components/nexus-orient/src/main/java/org/sonatype/nexus/orient/entity/AttachedEntityId.java
def cpId = cp.entityMetadata.id.identity
// asset of type: https://github.com/sonatype/nexus-public/blob/master/components/nexus-repository/src/main/java/org/sonatype/nexus/repository/storage/Asset.java
def cpAssets = transaction.browseAssets(cp).collect()
// document of type https://github.com/joansmith1/orientdb/blob/master/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocument.java
// _fields of type: https://github.com/joansmith1/orientdb/blob/master/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentEntry.java
// any field is of type ODocumentEntry.java
// append to map if it does not belong to the ignored versions
if (!(cp.entityMetadata.document._fields.version.value in ignoreVersions)) {
cpMap.put(cpId, cp)
cpAssetsMap.put(cpId, cpAssets)
}
}
// log info about the affected folder/service
log.info("cp map size: {}, versions: {}",
cpMap.values().size(),
cpMap.values().entityMetadata.document._fields.version.value)
// order desc by last_downloaded (default is asc)
log.debug("cp map assets of size: {}", cpAssetsMap.values().size())
def sortedFilteredList = cpAssetsMap.values()
.sort { it.entityMetadata.document._fields.last_downloaded?.value }
.reverse(true)
.drop(delAllButMostRecentNImages)
// list of cp ids from the assets that going to be deleted
def sortedAssetsCps = sortedFilteredList.entityMetadata.document._fields.component?.value?.flatten()
log.info("cp map assets size after filtering {}", sortedFilteredList.size())
// this will print the cps ids to delete
log.debug("elements to delete : sorted assets cps list {}", sortedAssetsCps)
// deleting components and their assets
cpMap.findAll { it.key in sortedAssetsCps }
.each { entry ->
log.info("deleting cp version {}", entry.value.entityMetadata.document._fields.version?.value)
// this will call delete asset internally, and by default will delete blob
transaction.deleteComponent(entry.value)
}
}
}
transaction.commit();
} catch (Exception e) {
log.warn("transaction failed {}", e.toString())
transaction.rollback()
} finally {
transaction.close();
}
}
}
new RepositoryProcessor(log, repository).processRepository()