使用Artifactory查询语言(AQL)进行高级清理

每个Artifactory管理员都有自己的方法和策略来管理Artifactory中的二进制文件,但是,清理工件和释放存储空间是每个管理员的共同需求。您可能知道,Artifactory确实提供了一些开箱即用的清理方法,例如删除完整版本限制快照数量而且删除未使用的缓存工件.尽管如此,对于许多用例来说,这些方法不够灵活,不能满足不同管理员可能具有的非常具体的删除需求。这将导致系统用户使用非常旧的、未使用的和不必要的工件,从而消耗大量的存储空间。

人工查询语言(AQL)是专门设计来查找工件存储在Artifactory的存储库基于任意数量的搜索条件。它的语法提供了一种简单的方法来制定复杂的查询,这些查询指定任意数量的搜索条件、过滤器、排序选项和字段输出参数。AQL被公开为RESTful API,在可能的情况下,它使用数据流提供输出数据,从而获得极快的响应时间和较低的内存消耗。

数据存储磁盘

Artifactory的UI或日志中出现的“数据存储磁盘过高”警告听起来很熟悉吗?在这一点上,大多数人会发现自己会说“我希望JFrog能够开发一种能够满足我的确切需求的清理方法”,但由于每个人的确切需求都是不同的,我们不能真正做到这一点。我们可以吗?当然可以。下面运行任何AQL查询的groovy脚本正是这样做的。您所要做的就是提供清理的标准。

让我们看一个例子。假设我想删除所有符合以下条件的文件:

  • 它们是由“jenkins-builder”用户创建的最大的100个文件。
  • 它们的扩展名是tar、zip或rpm。
  • 它们从未被下载过。
  • 它们的大小大于100Mb。
  • 它们被标记为属性“qa=approved”。

返回符合所有这些条件的文件的AQL查询如下所示:

物品。找到({“类型”:“文件”、“created_by”:“jenkins-builder”、“大小”:{$ gt:“100000000”},“stat.downloads”:{零}" $ eq”:,“@qa”:“批准”,“或“美元:[{"名称":{" $匹配":" * . tar "}}, {" name ":{" $匹配”:“* . zip”}},{"名称":{" $匹配”:“*。rpm "}} ] } ) . 排序({“美元desc”:(“大小”、“名称”)}).limit (100)

下面是删除AQL查询返回的文件的groovy脚本..你所需要做的就是:

  • 将" query "参数值替换为上面的参数值(或使用您自己的AQL查询)
  • 将“artifactoryURL”的值替换为您自己的Artifactory服务器URL
  • 将' admin:password '中的凭证替换为您自己的凭证。
  • 设置“dryRun”参数。如果设置为“true”(如下所示),那么文件将只会被列出。如果设置为“false”,文件真的会被删除(所以我建议先运行一个“true”)

它是如何工作的?该脚本将AQL查询发送到Artifactory,作为响应,以JSON对象的形式接收满足我们所有标准的工件列表。对于列表中的每个工件,它构造工件路径(参见constructPath()),如果“dryRun,则发送另一个http DELETE请求。如果服务器不可访问,或者用户没有足够的权限,它将打印一条消息到输出。

(有关最新的脚本,请访问我们的GitHub账户

@Grab(group = 'org.codehaus.groovy.modules。http-builder', module = 'http-builder', version = '0.6') import groovyx.net.http.RESTClient import groovyx.net.http.HttpResponseException import org.apache.http.conn.HttpHostConnectException /** *由shaybagants于4/30/15创建。*/ def query = 'items.find({"type":"file","name":{"$match":"jfrog-artifact-3.*.tar.gz"}})' //用你的AQL查询替换这个def artifactoryURL = 'https://localhost:8081/artifactory/' //用你的Artifactory服务器替换这个def restClient = new restClient (artifactoryURL) restClient. find({"type":"file","name":{"$match":"jfrog-artifact-3.*.tar.gz"}})setheader([“授权”:“基本”+“admin,密码”.getBytes(“iso - 8859 - 1”).encodeBase64())) / /将“管理:密码”替换为您自己的凭证def dryRun = true / /将值设置为false实际上如果你想让脚本删除构件def itemsToDelete = getAqlQueryResult (restClient、查询)如果(itemsToDelete ! = null & & itemsToDelete.size() > 0){删除(restClient, itemsToDelete dryRun)} {println(“没有删除 ') } /** * 发送AQL Artifactory并收集响应。*/ public List getAqlQueryResult(RESTClient RESTClient, String查询){def response try {response = RESTClient . List getAqlQueryResult(RESTClient RESTClient, String查询)post(路径:'api/search/aql',正文:query, requestContentType: 'text/plain')} catch (Exception e) {println(e.m esage)} if (response != null && response. getdata ()) {def results = [];.results response.getData()。each {results.add(constructPath(it))}返回结果;} else return null} /** *构造返回项的完整路径。*如果路径为'。' (file is on the root) we ignores it and construct the full path from the repo and the file name only */ public constructPath(HashMap item) { if (item.path.toString().equals(".")) { return item.repo + "/" + item.name } return item.repo + "/" + item.path + "/" + item.name } /** * Send DELETE request to Artifactory for each one of the returned items */ public delete(RESTClient restClient, List itemsToDelete, def dryRun) { dryMessage = (dryRun) ? "*** This is a dry run ***" : ""; itemsToDelete.each { println("Trying to delete artifact: '$it'. $dryMessage") try { if (!dryRun) { restClient.delete(path: it) } println("Artifact '$it' has been successfully deleted. $dryMessage") } catch (HttpResponseException e) { println("Cannot delete artifact '$it': $e.message" + ", $e.statusCode") } catch (HttpHostConnectException e) { println("Cannot delete artifact '$it': $e.message") } } }