Очистить старый релиз от Nexus 3
Я использую Nexus 3 и хочу удалить свои старые выпуски. В Nexus 2 есть запланированное задание, которое называется Remove Releases From Repository
, Кажется, что эта задача отсутствует в Nexus 3.
Как мы можем удалить старый релиз из Nexus 3?
Спасибо
7 ответов
Начиная с выпуска nexus 3 вы можете развертывать скрипты groovy в менеджере nexus. Просто создайте новую задачу сценария выполнения и используйте следующий сценарий:
import org.sonatype.nexus.repository.storage.StorageFacet;
import org.sonatype.nexus.common.app.GlobalComponentLookupHelper
import org.sonatype.nexus.repository.maintenance.MaintenanceService
import org.sonatype.nexus.repository.storage.ComponentMaintenance
import org.sonatype.nexus.repository.storage.Query;
import org.sonatype.nexus.script.plugin.RepositoryApi
import org.sonatype.nexus.script.plugin.internal.provisioning.RepositoryApiImpl
import com.google.common.collect.ImmutableList
import org.joda.time.DateTime;
import org.slf4j.Logger
// ----------------------------------------------------
// delete these rows when this script is added to nexus
RepositoryApiImpl repository = null;
Logger log = null;
GlobalComponentLookupHelper container = null;
// ----------------------------------------------------
def retentionDays = 30;
def retentionCount = 10;
def repositoryName = 'maven-releases';
def whitelist = ["org.javaee7.sample/javaee7-simple-sample", "org.javaee7.next/javaee7-another-sample"].toArray();
log.info(":::Cleanup script started!");
MaintenanceService service = container.lookup("org.sonatype.nexus.repository.maintenance.MaintenanceService");
def repo = repository.repositoryManager.get(repositoryName);
def tx = repo.facet(StorageFacet.class).txSupplier().get();
def components = null;
try {
tx.begin();
components = tx.browseComponents(tx.findBucket(repo));
}catch(Exception e){
log.info("Error: "+e);
}finally{
if(tx!=null)
tx.close();
}
if(components != null) {
def retentionDate = DateTime.now().minusDays(retentionDays).dayOfMonth().roundFloorCopy();
int deletedComponentCount = 0;
int compCount = 0;
def listOfComponents = ImmutableList.copyOf(components);
def previousComp = listOfComponents.head().group() + listOfComponents.head().name();
listOfComponents.reverseEach{comp ->
log.info("Processing Component - group: ${comp.group()}, ${comp.name()}, version: ${comp.version()}");
if(!whitelist.contains(comp.group()+"/"+comp.name())){
log.info("previous: ${previousComp}");
if(previousComp.equals(comp.group() + comp.name())) {
compCount++;
log.info("ComCount: ${compCount}, ReteCount: ${retentionCount}");
if (compCount > retentionCount) {
log.info("CompDate: ${comp.lastUpdated()} RetDate: ${retentionDate}");
if(comp.lastUpdated().isBefore(retentionDate)) {
log.info("compDate after retentionDate: ${comp.lastUpdated()} isAfter ${retentionDate}");
log.info("deleting ${comp.group()}, ${comp.name()}, version: ${comp.version()}");
// ------------------------------------------------
// uncomment to delete components and their assets
// service.deleteComponent(repo, comp);
// ------------------------------------------------
log.info("component deleted");
deletedComponentCount++;
}
}
} else {
compCount = 1;
previousComp = comp.group() + comp.name();
}
}else{
log.info("Component skipped: ${comp.group()} ${comp.name()}");
}
}
log.info("Deleted Component count: ${deletedComponentCount}");
}
https://github.com/xninjaxelitex/nexus3-cleanup-release-artifact
Этот скрипт очистит ваш репозиторий Nexus по указанным параметрам вверху.
Посмотрите на этот скрипт как на основу для работы:
https://gist.github.com/emexelem/bcf6b504d81ea9019ad4ab2369006e66 от emexelem
import org.sonatype.nexus.repository.storage.StorageFacet;
import org.sonatype.nexus.repository.storage.Query;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
def fmt = DateTimeFormat.forPattern('yyyy-MM-dd HH:mm:ss');
// Get a repository
def repo = repository.repositoryManager.get('nuget-releases');
// Get a database transaction
def tx = repo.facet(StorageFacet).txSupplier().get();
// Begin the transaction
tx.begin();
// Search assets that havn't been downloaded for more than three months
tx.findAssets(Query.builder().where('last_downloaded <').param(DateTime.now().minusMonths(3).toString(fmt)).build(), [repo]).each { asset ->
def component = tx.findComponent(asset.componentId());
// Check if there is newer components of the same name
def count = tx.countComponents(Query.builder().where('name').eq(component.name()).and('version >').param(component.version()).build(), [repo]);
if (count > 0) {
log.info("Delete asset ${asset.name()} as it has not been downloaded since 3 months and has a newer version")
tx.deleteAsset(asset);
tx.deleteComponent(component);
}
}
// End the transaction
tx.commit();
Итак, я столкнулся с проблемой нехватки дискового пространства, так как наши выпуски / сборки были сложены, поэтому я немного покопался в создании сценария для удаления старых сборок и пришел к следующему:
import org.sonatype.nexus.repository.storage.StorageFacet;
import org.sonatype.nexus.repository.storage.Query;
def repositoryName = 'Integration';
def maxArtifactCount = 20;
// Get a repository
def repo = repository.repositoryManager.get(repositoryName);
// Get a database transaction
def tx = repo.facet(StorageFacet).txSupplier().get();
try {
// Begin the transaction
tx.begin();
def previousComponent = null;
def uniqueComponents = [];
tx.findComponents(Query.builder().suffix(' ORDER BY group, name').build(), [repo]).each{component ->
if (previousComponent == null || (!component.group().equals(previousComponent.group()) || !component.name().equals(previousComponent.name()))) {
uniqueComponents.add(component);
}
previousComponent = component;
}
uniqueComponents.each {uniqueComponent ->
def componentVersions = tx.findComponents(Query.builder().where('group = ').param(uniqueComponent.group()).and('name = ').param(uniqueComponent.name()).suffix(' ORDER BY last_updated DESC').build(), [repo]);
log.info(uniqueComponent.group() + ", " + uniqueComponent.name() + " size " + componentVersions.size());
if (componentVersions.size() > maxArtifactCount) {
componentVersions.eachWithIndex { component, index ->
if (index > maxArtifactCount) {
log.info("Deleting Component ${component.group()} ${component.name()} ${component.version()}")
tx.deleteComponent(component);
}
}
}
}
} finally {
// End the transaction
tx.commit();
}
Это работает через репозиторий, ища все компоненты. Затем он работает через все версии (упорядоченные по последнему обновленному - я не мог определить порядок по номеру версии, но я думаю, что это должно быть в порядке), а затем удаляет любую из числа, превышающего число maxArtifactCount.
Надеюсь, что это может быть полезно - и если вы видите какие-либо проблемы, дайте мне знать.
Старая тема, но все еще актуальная тема.
После обновления Nexus 2.x до Nexus 3.x встроенная функция для хранения последних выпусков X, к сожалению, исчезла. Конечно, для этого есть запрос функции: https://issues.sonatype.org/browse/NEXUS-10821
Я попробовал сценарий Мэтта Харрисона (см. Ранее в этой ветке), но инструмент миграции в Nexus сбрасывал все значения last_updated на дату миграции, опять грустно.
Я попытался отсортировать выпуски по версиям с помощью ORDER BY version DESC, но это привело к беспорядку, когда версия 3.9.0 новее, чем 3.11.0 и так далее, что не подходит в моем сценарии.
Итак, я добавил в скрипт несколько вспомогательных списков, больше выходных данных (кредиты neil201) и сортировщик версий от founddrama ( https://gist.github.com/founddrama/971284) - и вуаля, у меня было хорошо работающее решение!
import org.sonatype.nexus.repository.storage.StorageFacet;
import org.sonatype.nexus.repository.storage.Query;
def repositoryName = 'myrepo';
def maxArtifactCount = 42;
log.info("==================================================");
log.info(":::Cleanup script started...");
log.info("==================================================");
log.info(":::Operating on Repository: ${repositoryName}");
log.info("==================================================");
// Get a repository
def repo = repository.repositoryManager.get(repositoryName);
// Get a database transaction
def tx = repo.facet(StorageFacet).txSupplier().get();
try {
// Begin the transaction
tx.begin();
int totalDelCompCount = 0;
def previousComponent = null;
def uniqueComponents = [];
tx.findComponents(Query.builder().suffix(' ORDER BY group, name').build(), [repo]).each{component ->
if (previousComponent == null || (!component.group().equals(previousComponent.group()) || !component.name().equals(previousComponent.name()))) {
uniqueComponents.add(component);
}
previousComponent = component;
}
uniqueComponents.each {uniqueComponent ->
def componentVersions = tx.findComponents(Query.builder().where('group = ').param(uniqueComponent.group()).and('name = ').param(uniqueComponent.name()).suffix(' ORDER BY last_updated DESC').build(), [repo]);
log.info("Processing Component: ${uniqueComponent.group()}, ${uniqueComponent.name()}");
def foundVersions = [];
componentVersions.eachWithIndex { component, index ->
foundVersions.add(component.version());
}
log.info("Found Versions: ${foundVersions}")
// version-sorting by founddrama
def versionComparator = { a, b ->
def VALID_TOKENS = /.-_/
a = a.tokenize(VALID_TOKENS)
b = b.tokenize(VALID_TOKENS)
for (i in 0..<Math.max(a.size(), b.size())) {
if (i == a.size()) {
return b[i].isInteger() ? -1 : 1
} else if (i == b.size()) {
return a[i].isInteger() ? 1 : -1
}
if (a[i].isInteger() && b[i].isInteger()) {
int c = (a[i] as int) <=> (b[i] as int)
if (c != 0) {
return c
}
} else if (a[i].isInteger()) {
return 1
} else if (b[i].isInteger()) {
return -1
} else {
int c = a[i] <=> b[i]
if (c != 0) {
return c
}
}
}
return 0
}
sortedVersions = foundVersions.sort(versionComparator)
log.info("Sorted Versions: ${sortedVersions}")
removeVersions = sortedVersions.dropRight(maxArtifactCount)
totalDelCompCount = totalDelCompCount + removeVersions.size();
log.info("Remove Versions: ${removeVersions}");
log.info("Component Total Count: ${componentVersions.size()}");
log.info("Component Remove Count: ${removeVersions.size()}");
if (componentVersions.size() > maxArtifactCount) {
componentVersions.eachWithIndex { component, index ->
if (component.version() in removeVersions) {
log.info("Deleting Component: ${component.group()}, ${component.name()} ${component.version()}")
// ------------------------------------------------
// uncomment to delete components and their assets
// tx.deleteComponent(component);
// ------------------------------------------------
}
}
}
log.info("==================================================");
}
log.info(" *** Total Deleted Component Count: ${totalDelCompCount} *** ");
log.info("==================================================");
} finally {
// End the transaction
tx.commit();
}
Теперь вы можете форкнуть этот скрипт на github: https://github.com/PhilSwiss/nexus-cleanup
У нас пока нет этой запланированной задачи, тем временем вы можете использовать функциональность Delete Component в пользовательском интерфейсе, если вам нужно вручную удалить выпуск.
Хотя этот пост довольно старый, у меня было аналогичное требование, но я хотел удалить старые артефакты / выпуски только на основе версий, поэтому нужно было найти способ их отсортировать и оставить только самые последние.
Это сценарий, который я придумал, он основан на некоторых из уже приведенных здесь примеров.
Обратите внимание, что это было специально для очистки репозиториев YUM, но должно работать для других типов с небольшими изменениями.
import org.sonatype.nexus.repository.storage.StorageFacet;
import org.sonatype.nexus.repository.storage.Query;
import com.google.common.collect.ImmutableList
import org.joda.time.format.DateTimeFormat;
import org.joda.time.DateTime;
import org.slf4j.Logger
///////////////////////////////
def retentionCount = 3;
def repositoryName = 'repo-name';
def whitelist = [].toArray();
///////////////////////////////
log.info("==================================================");
log.info(":::Cleanup script started...");
log.info("==================================================");
log.info(":::Operating on Repository: ${repositoryName}");
log.info("==================================================");
def repo = repository.repositoryManager.get(repositoryName);
def tx = repo.facet(StorageFacet.class).txSupplier().get();
def components = null;
try {
// Begin the transaction
tx.begin();
components = tx.browseComponents(tx.findBucket(repo));
if(components != null) {
int compCount = 0;
int delCompCount = 0;
int totalDelCompCount = 0;
def listOfComponents = ImmutableList.copyOf(components);
def previousComponent = null;
def uniqueComponents = [];
////////////////////////////////////////////////////////
final Long MAX_NUMBER = 10000L;
listOfComponents.each { component ->
if(!whitelist.contains(component.name())) {
if (previousComponent == null || !component.name().equals(previousComponent.name())) {
uniqueComponents.add(component);
}
previousComponent = component;
}
}
uniqueComponents.each { uniqueComponent ->
log.info("Starting Processing on Component: ${uniqueComponent.name()}");
def artifactVersions = [];
def numberArray = new ArrayList<Long>();
tx.findComponents(Query.builder().where('name = ').param(uniqueComponent.name()).build(), [repo]).each { component ->
def artifactVersion = component.version().replaceAll('-', '.');
artifactVersions.add(artifactVersion);
}
log.info("Found Versions: ${artifactVersions}");
for(ver in artifactVersions) {
String[] vers = ver.split('\\.');
Long num = 0;
for (int i = 0; i < vers.length; ++i) {
num = num + Long.valueOf(vers[i]) * (long) Math.pow(MAX_NUMBER, vers.length - i - 1);
}
numberArray.add(num);
}
numberArray = numberArray.sort(); //.reverse();
//log.info("Sorting the Versions: ${numberArray}");
def sortedArtifactVersions = [];
for (num in numberArray) {
List<Long> parts = new ArrayList<>();
while (num > 0) {
parts.add((long) (num % MAX_NUMBER));
num = Math.floorDiv(num, MAX_NUMBER);
}
String artifact = parts.reverse().join('.');
sortedArtifactVersions.add(artifact);
}
log.info("Sorted Versions: ${sortedArtifactVersions}");
compCount = sortedArtifactVersions.size();
def toRemoveArtifactVersions = [];
if (compCount > retentionCount) {
toRemoveArtifactVersions = sortedArtifactVersions.dropRight(retentionCount);
}
delCompCount = toRemoveArtifactVersions.size();
totalDelCompCount = totalDelCompCount + delCompCount;
log.info("Remove Versions: ${toRemoveArtifactVersions}");
log.info("Component Total Count: ${compCount}");
log.info("Component Remove Count: ${delCompCount}");
for (ver in toRemoveArtifactVersions) {
StringBuilder b = new StringBuilder(ver);
b.replace(ver.lastIndexOf("."), ver.lastIndexOf(".") + 1, "-" );
ver = b.toString();
tx.findComponents(Query.builder().where('name = ').param(uniqueComponent.name()).and('version = ').param(ver).build(), [repo]).each { component ->
log.info("Deleting Component: ${uniqueComponent.name()} ${ver}");
// ------------------------------------------------
// uncomment to delete components and their assets
// tx.deleteComponent(component);
// ------------------------------------------------
}
}
log.info("==================================================");
}
log.info(" *** Total Deleted Component Count: ${totalDelCompCount} *** ");
log.info("==================================================");
}
// End the transaction
tx.commit();
} catch(Exception e) {
log.info("Error: "+e);
tx.rollback();
} finally {
tx.close();
}
Я также попал в этот довольно старый пост о необходимости удалить старые артефакты в репозитории релизов в Nexus 3.
После миграции с Nexus 2 все поля LastUpdated были перезаписаны меткой времени импорта, и не все другие решения учитывают это. Как видно из сведений об артефакте при просмотре репозитория, полезная информация
last_modified
поле, содержащееся в
Attributes -> content
.
Начиная с опубликованных решений @ninjaxelite , благодаря @Phil Swiss и @neil201 , я попытался найти способ, рассматривая активы, а не компоненты (атрибуты включены в активы).
Требовалось сохранить как минимум N выпущенных версий, даже если срок хранения истек.
Я занимаюсь сортировкой извлеченных артефактов, где myApp-war-2.12.3.war считается более новым, чем myApp-war-2.2.3.war из-за буквальной сортировки, решаемой с использованием решения @founddrama, как и в других сообщениях.
Учтите, что следующее решение извлекает все записи и занимает много памяти и времени для сортировки и проверки всех элементов в репозитории каждый раз, когда запускается сценарий, я не гарантирую, что он правильно работает с большими репозиториями (проверено с 1,5 ТБ за 10 минут) . Любое улучшение характеристик приветствуется.
import org.sonatype.nexus.repository.storage.StorageFacet;
import org.sonatype.nexus.common.app.GlobalComponentLookupHelper
import org.sonatype.nexus.repository.maintenance.MaintenanceService
import org.sonatype.nexus.repository.storage.ComponentMaintenance
import org.sonatype.nexus.repository.storage.Query;
import org.sonatype.nexus.script.plugin.RepositoryApi
import org.sonatype.nexus.script.plugin.internal.provisioning.RepositoryApiImpl
import com.google.common.collect.Lists
import com.google.common.collect.Iterables
import org.joda.time.DateTime
import org.slf4j.Logger
// ----------------------------------------------------
// delete these rows when this script is added to nexus
//RepositoryApiImpl repository = null;
//Logger log = null;
//GlobalComponentLookupHelper container = null;
// ----------------------------------------------------
// ---------------------- CONFIG ------------------------------
// ATTENTION: This script is skilled for maven repos
// 5 Years of RetentionDays
def retentionDays = 1825;
def retentionCount = 3;
def repositoryName = 'Repository-Name';
def whitelist = ["org.javaee7.sample/javaee7-simple-sample", "org.javaee7.next/javaee7-another-sample"].toArray();
// ------------------------------------------------------------
log.info(":::Cleanup script of ${repositoryName} STARTED!");
MaintenanceService service = container.lookup("org.sonatype.nexus.repository.maintenance.MaintenanceService");
def repo = repository.repositoryManager.get(repositoryName);
def tx = repo.facet(StorageFacet.class).txSupplier().get();
def assets = null;
try {
tx.begin();
//CAREFUL!! This query extracts all Assets, do filter the search where possible
assets = tx.browseAssets(tx.findBucket(repo));
}catch(Exception e){
log.info("Error: "+e);
}finally{
if(tx!=null)
tx.close();
}
if(assets != null) {
def retentionDate = DateTime.now().minusDays(retentionDays).dayOfMonth().roundFloorCopy();
int deletedAssetsCount = 0;
int assetCount = 1;
List<Iterables> listOfAssets = Lists.newArrayList(assets);
//Base Path of each single project, it will be used for retention count (for each project it will not deleted versions at retentionCount amount)
def assetBasePath = listOfAssets.head().attributes().get('maven2').get('groupId')+"."+listOfAssets.head().attributes().get('maven2').get('artifactId');
def currentAsset = null;
def assetFilename = null;
// ----> ######## Asset List Sorting ##########
// Considering version number in filename, i.e. myApp-war-2.12.3.war is more recent than myApp-war-2.2.3.war
// version-sorting by founddrama
def versionComparator = { itemA, itemB ->
def VALID_TOKENS = /.-_/
def a = itemA.name().tokenize(VALID_TOKENS)
def b = itemB.name().tokenize(VALID_TOKENS)
for (i in 0..<Math.max(a.size(), b.size())) {
if (i == a.size()) {
return b[i].isInteger() ? -1 : 1
} else if (i == b.size()) {
return a[i].isInteger() ? 1 : -1
}
if (a[i].isInteger() && b[i].isInteger()) {
int c = (a[i] as int) <=> (b[i] as int)
if (c != 0) {
return c
}
} else if (a[i].isInteger()) {
return 1
} else if (b[i].isInteger()) {
return -1
} else {
int c = a[i] <=> b[i]
if (c != 0) {
return c
}
}
}
return 0
}
log.info("Extracted Asset List Sorting ...");
listOfAssets = listOfAssets.sort(versionComparator);
log.info("Extracted Asset List Sorted");
// <---- ######## Asset List Sorting ##########
listOfAssets.reverseEach{asset ->
if (asset.attributes().get('maven2').get('asset_kind').equals("REPOSITORY_METADATA")){
//The metadata files are skipped by default
currentAsset = null;
}else if (
asset.attributes().get('maven2').get('groupId') != null
&& asset.attributes().get('maven2').get('artifactId') != null
){
// By default the asset basePath it will considered as groupId + artifactId maven attributes
currentAsset = asset.attributes().get('maven2').get('groupId')+"."+asset.attributes().get('maven2').get('artifactId');
assetFilename = asset.attributes().get('maven2').get('version')+"."+asset.attributes().get('maven2').get('extension');
}else{
// Otherwise, for raw files (and i.e. maven-metadata.xml) the same basePath it is decoded from filename
// Obvious, it can be used this way in each case avoiding previous code, but I consider it as the second chance
// Cut from penultimate occurrence of / to the end, then replace / with .
currentAsset = asset.name().replaceAll("(.*)/([^/]+)/([^/]+)", '$1').replaceAll("/", ".");
assetFilename = asset.name().replaceAll("(.*)/([^/]+)", '$2');
}
if (currentAsset != null && !whitelist.contains(currentAsset)){
log.info("Processing Asset : ${currentAsset}, filename: ${assetFilename}");
log.info("AssetBasePath: ${assetBasePath}");
if(assetBasePath.equals(currentAsset)) {
log.info("AssetCount: ${assetCount}, Retention: ${retentionCount}");
if (assetCount > retentionCount) {
def lastModifiedDate = asset.attributes().get('content').get('last_modified');
DateTime lastModifiedDateTime = lastModifiedDate==null?null:new DateTime(lastModifiedDate);
log.debug("AssetLastModified: ${lastModifiedDateTime} - RetentionDate: ${retentionDate}");
if (retentionDate.isAfter(lastModifiedDateTime)) {
log.info("AssetLastModified ${lastModifiedDateTime} isOldestThan than RetentionDate ${retentionDate}");
log.info("Deleting: ${currentAsset}, filename: ${assetFilename}");
// ------------------------------------------------
// uncomment to delete assets
// service.deleteAsset(repo, asset);
// ------------------------------------------------
log.info("Asset DELETED");
deletedAssetsCount++;
}
}
assetCount++;
} else {
assetCount = 1;
assetBasePath = currentAsset;
}
}else{
log.info("Asset skipped due to whitelisted or Format not supported: ${asset.name()}");
}
}
log.info("TASK END - TOTAL Deleted Asset count: ${deletedAssetsCount}");
}
Решение предназначено для репозитория Maven, но с некоторыми изменениями, я надеюсь, оно может быть полезно для всех типов репо.
Не забудьте также запланировать задачу «Компактное хранилище больших двоичных объектов», чтобы освободить место на диске после удаления артефакта.