Searching Artifactory with REST API

Hi, this is my first post that I decided to capture my research into somewhere I can refer later and also share to everyone that might run into the same interest on the specific Use Cases that my knowledge can help.
Today we will start with search Artifactory via REST API.

Use Case:

I'm working on a task to install an MSI package that was published into artifactory automatically. This task will allow user to input whatever package name with specific version or apply a latest version.
For specific version, we can construct the url path based on provided GAV and repository. However, if we want to apply latest version, for example using X.X.+ then retrieve the latest version X.X.100. So we need a way to retrieve a latest version of an artifact from artifactory under specific repository (and / or group)


Solution:

There are some solution for the Use Case above

- Option 1: Using Gradle Dependency Lock Plugin. This plugin will create a file of the dependency that are using in your application so you can trace back the latest version which was used as dependency. This is an alternative on our specific context so I don't dig into detail on this option

- Option 2: Using Artifactory REST API. JFrog Artifactory provides even more powerful REST API capability then its UI functionality so we can find a HUGE functionality from its REST API. These APIs can be invoked by any standard endpoint, a simple tool is cURL.
For my story, we can use the search latest version API as below
GET /api/search/latestVersion?g=org.acme&a=artifact&v=1.0-SNAPSHOT&repos=libs-snapshot-local
curl.exe -u [user]:[password] -X GET "http://[yourserver]:8081/artifactory/api/search/latestVersion?g=[group]&a=[artifact]&v=[version*]&remote=1&repos=[repo]"

Sample Code:
def user = 'user'
def pwd = 'password'
def searchAPI = 'http://server:8081/artifactory/api/search/latestVersion?'
def searchRepo = 'repo'
def searchGroup = 'group' //com.level1.level2
def searchFile = 'file'
def searchVersion = 'version'
def searchQuery = /g=${searchGroup}&a=${searchFile}&v=${searchVersion}*&remote=1&repos=${searchRepo}/
def String sCommand = "curl.exe -u ${user}:${pwd} -X GET \"${searchAPI}${searchQuery}\""
println sCommand  
def sOut = new StringBuffer()
def sErr = new StringBuffer()
def oProc = sCommand.execute()
oProc.consumeProcessOutput(sOut, sErr)
oProc.waitForOrKill(3000)
def String sReturn = sOut.toString().trim()
println sReturn

- Option 3: Using Artifactory Query Language. This is another powerful tool from artifactory that allow you to provide a query with aql syntax and retrieve the result in json format.
You can refer into this thread for detail on how to construct a query string. As the command from this discussion, you can create the aql query into a file then pass this file into curl command @filepath (bash shell).
For my story, I will use Groovy to construct a command line to trigger the curl command with an aql query. This will lead into a terrible story of escaping double quotes ("), dollar sign ($) in the query string and you also have to keep the double quotes within the query string for json format query is also an headache.
So I will use dollar slashy string to build this query string
searchQuery = $/items.find({ \"repo\" : \"${searchRepo}\", \"name\" : {\"$$match\" : \"${searchFile}-${searchVersion}*\"} }).include(\"name\", \"path\").sort({\"$$desc\" : [\"name\"]}).limit(1)/$

More detail on my string builder:
$/ ... /$ : my dollar slashy string
\" : keep the double quotes in another "" string
$$: escape dollar sign
query syntax: search from [repo] which item match with pattern [file]-[version]* then return the result with "name" and "path" of the item, sort this result by name and get the top of the result list = latest version.

Sample code:
import groovy.json.JsonSlurper;

def user = 'user'
def pwd = 'password'
def searchAPI = 'http://server:8081/artifactory/api/search/aql'
def searchRepo = 'repo'
def searchGroup = 'group/level'
def searchFile = 'filename'
def searchVersion = 'version'
def searchQuery = $/items.find({ \"repo\" : \"${searchRepo}\", \"path\" : {\"$$match\" : \"$searchGroup/*\"}, \"name\" : {\"$$match\" : \"${searchFile}-${searchVersion}*\"} }).include(\"name\", \"path\").sort({\"$$desc\" : [\"name\"]}).limit(1)/$
def String sCommand = "curl.exe -u ${user}:${pwd} -X POST ${searchAPI} -H \"content-type: application/json\" -d \"" + searchQuery + "\""
println sCommand  
def sOut = new StringBuffer()
def sErr = new StringBuffer()
def oProc = sCommand.execute()
oProc.consumeProcessOutput(sOut, sErr)
oProc.waitForOrKill(3000)
def String sReturn = sOut.toString().trim()
def jsonList = new JsonSlurper().parseText(sReturn)
println jsonList.results.path
println jsonList.results.name

I can run my task with option 2 and 3, so hope this will help.

Note:
- I use a quick /dirty way to trigger REST API by using groovy execute command with curl. We can use RESTClient object to init the REST invoke.
- With search by REST API, the result will the the version string or an json object for error result so you have to parse the result for error message or get the version directly or the string result doesn't have "404"
- Make sure your user has sufficient permission to search on the repositories

Comments