【问题标题】:Purge old release from Nexus 3从 Nexus 3 中清除旧版本
【发布时间】:2017-04-06 04:21:54
【问题描述】:

我使用 Nexus 3,我想删除我的旧版本。在 Nexus 2 中有一个名为Remove Releases From Repository 的计划任务。 Nexus 3 中似乎缺少此任务。

我们如何从 Nexus 3 中删除旧版本?

谢谢

【问题讨论】:

标签: nexus nexus3


【解决方案1】:

我也陷入了这篇相当老的帖子,需要删除 Nexus 3 的发布存储库中的旧工件。

从 Nexus 2 迁移后,所有 LastUpdated 字段都被导入时间戳覆盖,并非所有其他解决方案都考虑到这一点。从存储库浏览的工件详细信息中可以看出,有用的信息是Attributes -> content 中包含的last_modified 字段。

@ninjaxelite 发布的解决方案开始,感谢@Phil Swiss@neil201,我试图通过考虑资产而不是组件(属性包含在资产中)找到一种方法。
要求是如果保留期已结束,请至少保留 N 个已发布版本。
我处理提取的工件的排序,由于文字排序,myApp-war-2.12.3.war 被认为比 myApp-war-2.2.3.war 更新,在其他帖子中使用 @founddrama 解决方案解决。

考虑到以下解决方案会在每次安排脚本时提取所有记录并花费大量内存和时间来对存储库中的所有项目进行排序和检查,我不能确保它在大型存储库中正常工作(在 1.5TB 中测试) 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 存储库,但经过一些更改,我希望它对所有类型的存储库都有用。
记得安排“压缩 blob 存储”任务,以便在工件删除后释放磁盘空间。

【讨论】:

    【解决方案2】:

    将此脚本视为使用的基础:

    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();
    

    【讨论】:

      【解决方案3】:

      旧线程,但仍然是当前主题。

      从 Nexus 2.x 升级到 Nexus 3.x 后,用于保存最新 X 版本的内置功能遗憾地消失了。 当然有一个功能请求:https://issues.sonatype.org/browse/NEXUS-10821

      我尝试了 Matt Harrison 的脚本(请参阅此线程的前面部分), 但是 Nexus 中的迁移工具将所有 last_updated 值重置为迁移日期,再次悲伤。

      我尝试通过“ORDER BY version DESC”按 version 对版本进行排序,但这导致一团糟,其中版本 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 上 fork 这个脚本:https://github.com/PhilSwiss/nexus-cleanup

      【讨论】:

        【解决方案4】:

        虽然这篇文章已经很老了,但我也有类似的要求,但只想根据版本删除旧的工件/版本,因此需要找到一种方法来对它们进行排序并只保留最新的。

        这是我提出的脚本,它基于此处已经给出的一些示例。

        请注意,这是专门用于清理 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();
        }
        

        【讨论】:

          【解决方案5】:

          自 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 存储库。

          【讨论】:

          • 如果我尝试通过 api 上的 curl 调用运行它,我会收到以下错误:org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:\nScript12.groovy: 1: Ambiguous expression could可以是无参数闭包表达式,也可以是孤立的开放代码块;\n 解决方案:添加显式闭包参数列表,例如{it -> ...},或者通过给它一个标签来强制它被视为一个开放块,例如L:{...} @ 第 1 行第 1 列。\n {\n ^\n\n1 错误\n"
          • 您是否删除了脚本顶部标记的代码部分?我只在 Nexus 中直接测试过它,从来没有使用 curl。
          • +1 此解决方案有效。不要忘记按照脚本中的说明修改代码和注释/取消注释行。
          • delete api 在我们的版本中似乎有所改变。我将 service.deleteComponent 替换为:try{ tx = repo.facet(StorageFacet.class).txSupplier().get(); tx.begin(); tx.deleteComponent(comp); }finally{ if(tx!=null){ tx.close(); } }
          • @ninjaxelite ,运行此脚本是否足以释放磁盘空间?还是需要一些额外的操作?我使用相同的机制 (service.deleteComponent()) 运行我的脚本来删除单个包,并且该包似乎从 nexus 中消失了,但我没有注意到我的 nexus 上有任何释放的磁盘空间
          【解决方案6】:

          所以,当我们的版本/构建堆积起来时,我遇到了磁盘空间不足的问题,因此我在创建脚本以删除旧构建时进行了一些挖掘,并得到了这个:

          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 数字。

          希望这可能有用 - 如果您发现任何问题,请告诉我。

          【讨论】:

            【解决方案7】:

            我们尚未构建此计划任务,与此同时,如果您需要手动删除版本,可以使用 UI 中的删除组件功能。

            【讨论】:

            • 感谢您的回答。有没有办法手动删除已发布的组件?我们有很多组件,一个一个地点击删除按钮很乏味。
            • 目前还没有通过 UI 批量处理。然而,有可能编写一个使用脚本 API 执行此操作的 groovy 脚本。
            • 这里有什么消息吗?安排好了吗?
            • 此清除功能更为重要,因为在 nexus 3 中使用 blob 存储时,我们无法直接从文件系统中清除工件。我查看了books.sonatype.com/nexus-book/3.0/reference/scripting.htmlgithub.com/sonatype/nexus-book-examples/tree/nexus-3.x/…,但没有找到任何明显的方法来编写清除一批版本的脚本。您能提供更精确的起点吗?
            • 我的一位同事编写了这个脚本来清理旧版本,我希望它对其他人有用:
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-01-08
            • 1970-01-01
            • 2023-04-02
            • 1970-01-01
            • 2017-11-16
            相关资源
            最近更新 更多