Artifactory aql: find builds of job with given property - artifactory

I am trying to query which build number(s) produced artifacts from build foo with artifact property vcs.Revision=aabbccddee123456.
In Artifactory 5.1.3.
I was trying like this so far:
curl -u user:apikey -i -X POST https://artifactory.foobar.com/artifactory/api/search/aql -H "content-type:text/plain" -T query.json
query.json:
builds.find(
{
"module.artifact.item.repo":"snapshot-local",
"name":"foo",
"module.artifact.item.#vcs.Revision":"aabbccddee123456"
}
)
However, none of these 3 lines seem individually correct:
builds.find({"module.artifact.item.repo":"snapshot-local"})
returns nothing,
builds.find({"name":"foo"})
returns the same empty response,
builds.find({"module.artifact.item.#vcs.Revision":"aabbccddee123456"}) also returns this:
{
"results" : [ ],
"range" : {
"start_pos" : 0,
"end_pos" : 0,
"total" : 0
}
}
What am I doing wrong here? I do see in the webapp the builds I published with this name, and with the correct artifact properties.

Here's a working solution that will give build numbers (since giving admin rights to query builds is not a solution for us):
query.json:
items.find(
{
"repo":"snapshot-local",
"artifact.module.build.name":"foo",
"artifact.item.#vcs.Revision":"aabbccddee123456"
}
).include("artifact.module.build.number")
This returns a list of all the artifacts that were built with the relevant properties, with the build number attached, e.g:
{
"results" : [ {
"repo" : "snapshot-local",
"path" : "foo/42",
"name" : "a.out",
"type" : "file",
"size" : 123456789,
"created" : "2018-07-05T12:34:56.789+09:00",
"created_by" : "jenkins",
"modified" : "2018-07-05T12:34:56.789+09:00",
"modified_by" : "jenkins",
"updated" : "2018-07-05T12:34:56.789+09:00",
"artifacts" : [ {
"modules" : [ {
"builds" : [ {
"build.number" : "42"
} ]
} ]
} ]
},
[SNIP]
}
],
"range" : {
"start_pos" : 0,
"end_pos" : 30,
"total" : 30
}
}
I can then parse this to extract build.number.

Certain AQL queries requires a user with admin permissions.
To ensure that non-privileged users do not gain access to information without the right permissions, users without admin privileges have the following restrictions:
The primary domain in the query may only be item.
The following three fields must be included in the include directive: name, repo, and path.
In your case, you are using the build domain in the query which requires admin permissions

Related

JFrog Artifactory AQL - Pagination mechanism

I am currently trying to query Artifactory for 25 results.
So I used:
items.find({"name" : {"$match":"somefile*"}).limit(25)
But I need to get also the Total result for calculate Total results.
If there is 500 results, i want to get it also.
For Example:
500/25 = 20 pages.
You can use the search command of the JFrog CLI, with the --count option to only get the number of results.
Or, using AQL through the REST API, you can first query without limit to get the total number of results, by looking at the total count at the range field:
{
"results" : [
{
"repo" : "libs-release-local",
"path" : "org/jfrog/artifactory",
"name" : "artifactory.war",
"type" : "item type",
"size" : "75500000",
"created" : "2015-01-01T10:10;10",
"created_by" : "Jfrog",
"modified" : "2015-01-01T10:10;10",
"modified_by" : "Jfrog",
"updated" : "2015-01-01T10:10;10"
}
],
"range" : {
"start_pos" : 0,
"end_pos" : 1,
"total" : 1 // <----
}
}

Force Artifactory to use numerical comparison when searching?

I am trying to find the latest (or earliest, depending on comparison operator) version of an RPM package (the RPM bit is important). I am using AQL query similar to this one:
items.find(
{ "$and" : [
{ "#rpm.metadata.name": { "$eq": "awesome_package"}},
{ "#rpm.metadata.version": { "$gte": "19.300.0.58"}} ]
})
.include("#rpm.metadata.version")
.sort( { "$asc": [ "name" ]})
As already answered by Artifactory KnowledgeBase, it's impossible to sort on properties, so instead of just sorting on #rpm.metadata.version and taking the first top result by using .limit(1) I must use property condition in the find clause.
It appears though that Artifactory's built-in comparison is purely lexicographic, so for the query above I get the following result:
{
"results" : [ {
"repo" : "yum-private-local",
"path" : "some/path",
"name" : "awesome_package-19.300.0.9-1.noarch.rpm",
"properties" : [ {
"key" : "rpm.metadata.version",
"value" : "19.300.0.9"
} ]
},{
"repo" : "yum-private-local",
"path" : "some/path",
"name" : "awesome_package-19.300.0.58-0.noarch.rpm",
"properties" : [ {
"key" : "rpm.metadata.version",
"value" : "19.300.0.58"
} ]
},{
"repo" : "yum-private-local",
"path" : "some/path",
"name" : "awesome_package-19.300.0.59-0.noarch.rpm",
"properties" : [ {
"key" : "rpm.metadata.version",
"value" : "19.300.0.59"
} ]
} ],
"range" : {
"start_pos" : 0,
"end_pos" : 3,
"total" : 3
}
}
This result includes version 19.300.0.9, which, according to RPM spec, is older than what I am searching for (>= 19.300.0.58) and shouldn't be included in the results, but Artifactory finds it nonetheless, most likely due to its search comparisons being lexicographic.
Also note the ordering of the results, which does appear to use numerical sorting (version "19.300.0.9" comes before "19.300.0.58" and "19.300.0.59").
Question: is it possible to force Artifactory to use numerical (SemVer) comparison in search criteria? If not, is there any other way I can exclude irrelevant versions from the result list?
Although not in lines with what is asked but instead of name sorting if done by the created field would also be helpful.
created:

Artifactory: count of images in docker registry?

I am quite a new user of artifactory and at the moment would like to check the number of docker images in an already existing setup. I have several docker registries in artifactory and would like to automate this check. Is there an api for it maybe?
Thanks for all help :)
BR,
Rafal.
You can use AQL for searching all the manifest.json files (since each image has exactly one such file).
For example (using curl):
curl -XPOST https://your.artifactory.domain/api/search/aql -T aql.json -u user:pass
Where aql.json is:
items.find({"repo":"docker-local","name":"manifest.json","path":{"$match":"*"}}).include("repo","path","name")
The query above searches for all the manifest.json files in the docker-local repository under any path (match *). It returns the following (example):
{
"results" : [ {
"repo" : "docker-local",
"path" : "hello-world/latest",
"name" : "manifest.json"
}, {
"repo" : "docker-local",
"path" : "hello-world/1.0",
"name" : "manifest.json"
} ],
"range" : {
"start_pos" : 0,
"end_pos" : 2,
"total" : 2
}
}
You can take the range.total as the number of images.
HTH,
Yinon

Firebase: structuring data via per-user copies? Risk of data corruption?

Implementing an Android+Web(Angular)+Firebase app, which has a many-to-many relationship: User <-> Widget (Widgets can be shared to multiple users).
Considerations:
List all the Widgets that a User has.
A User can only see the Widgets which are shared to him/her.
Be able to see all Users to whom a given Widget is shared.
A single Widget can be owned/administered by multiple Users with equal rights (modify Widget and change to whom it is shared). Similar to how Google Drive does sharing to specific users.
One of the approaches to implement fetching (join-style), would be to go with this advice: https://www.firebase.com/docs/android/guide/structuring-data.html ("Joining Flattened Data") via multiple listeners.
However I have doubts about this approach, because I have discovered that data loading would be worryingly slow (at least on Android) - I asked about it in another question - Firebase Android: slow "join" using many listeners, seems to contradict documentation .
So, this question is about another approach: per-user copies of all Widgets that a user has. As used in the Firebase+Udacity tutorial "ShoppingList++" ( https://www.firebase.com/blog/2015-12-07-udacity-course-firebase-essentials.html ).
Their structure looks like this:
In particular this part - userLists:
"userLists" : {
"abc#gmail,com" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"listName" : "Test List 1 Rename 2",
"owner" : "xyz#gmail,com",
"timestampCreated" : {
"timestamp" : 1456950573084
},
"timestampLastChanged" : {
"timestamp" : 1457044229747
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044229747
}
}
},
"xyz#gmail,com" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"listName" : "Test List 1 Rename 2",
"owner" : "xyz#gmail,com",
"timestampCreated" : {
"timestamp" : 1456950573084
},
"timestampLastChanged" : {
"timestamp" : 1457044229747
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044229747
}
},
"-KByb0imU7hFzWTK4eoM" : {
"listName" : "List2",
"owner" : "xyz#gmail,com",
"timestampCreated" : {
"timestamp" : 1457044332539
},
"timestampLastChanged" : {
"timestamp" : 1457044332539
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044332539
}
}
}
},
As you can see, the copies of shopping list "Test List 1 Rename 2" info appears in two places (for 2 users).
And here is the rest for completeness:
{
"ownerMappings" : {
"-KBt0MDWbvXFwNvZJXTj" : "xyz#gmail,com",
"-KByb0imU7hFzWTK4eoM" : "xyz#gmail,com"
},
"sharedWith" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"abc#gmail,com" : {
"email" : "abc#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
}
}
},
"shoppingListItems" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"-KBt0heZh-YDWIZNV7xs" : {
"bought" : false,
"itemName" : "item",
"owner" : "xyz#gmail,com"
}
}
},
"uidMappings" : {
"google:112894577549422030859" : "abc#gmail,com",
"google:117151367009479509658" : "xyz#gmail,com"
},
"userFriends" : {
"xyz#gmail,com" : {
"abc#gmail,com" : {
"email" : "abc#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
}
}
},
"users" : {
"abc#gmail,com" : {
"email" : "abc#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
},
"xyz#gmail,com" : {
"email" : "xyz#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Karol Depka",
"timestampJoined" : {
"timestamp" : 1456952940258
}
}
}
}
However, before I jump into implementing a similar structure in my app, I would like to clarify a few doubts.
Here are my interrelated questions:
In their ShoppingList++ app, they only permit a single "owner" - assigned in the ownerMappings node. Thus no-one else can rename the shopping list. I would like to have multiple "owners"/admins, with equal rights. Would such a keep-copies-per-user structure still work for multiple owner/admin users, without risking data corruption/"desynchronization" or "pranks"?
Could data corruption arise in scenarios like this: User1 goes offline, renames Widget1 to Widget1Prim. While User1 is offline, User2 shares Widget1 to User3 (User3's copy would not yet be aware of the rename). User1 goes online and sends the info about the rename of Widget1 (only to his own and User2's copies, of which the client code was aware at the time of the rename - not updating User3's copy). Now, in a naive implementation, User3 would have the old name, while the others would have the new name. This would probably be rare, but still worrying a bit.
Could/should the data corruption scenario in point "2." be resolved via having some process (e.g. on AppEngine) listening to changes and ensuring proper propagation to all user copies?
And/or could/should the data corruption scenario in point "2." be resolved via implementing a redundant listening to both changes of sharing and renaming, and propagating the changes to per-user copies, to handle the special case? Most of the time this would not be necessary, so it could result in performance/bandwidth penalty and complicated code. Is it worth it?
Going forward, once we have multiple versions deployed "in the wild", wouldn't it become unwieldy to evolve the schema, given how much of the data-handling responsibility lies with the code in the clients? For example if we add a new relationship, that the older client versions don't yet know about, doesn't it seem fragile? Then, back to the server-side syncer-ensurerer process on e.g. AppEngine (described in question "3.") ?
Would it seem like a good idea, to also have a "master reference copy" of every Widget / shopping-list, so as to give good "source of truth" for any syncer-ensurerer type of operations that would update per-user copies?
Any special considerations/traps/blockers regarding rules.json / rules.bolt permissions for data structured in such a (redundant) way ?
PS: I know about atomic multi-path updates via updateChildren() - would definitely use them.
Any other hints/observations welcome. TIA.
I suggest having only one copy of a widget for the entire system. It would have an origin user ID, and a set of users that have access to it. The widget tree can hold user permissions and change history. Any time a change is made, a branch is added to the tree. Branches can then be "promoted" to the "master" kind of like GIT. This would guarantee data integrity because past versions are never changed or deleted. It would also simplify your fetches... I think :)
{
users:[
bob:{
widgets:[
xxx:{
widgetKey: xyz,
permissions: *,
lastEdit...
}
]
}
...
]
widgets:[
xyz:{
masterKey:abc,
data: {...},
owner: bob,
},
...
]
widgetHistory:[
xyz:[
v1:{
data:{...},
},
v2,
v3
]
123:[
...
],
...
]
}

Artifactory API AQL "Displaying Specific Fields"

According to below link, Artifactory AQL allows "Displaying of specific fields" via REST API by returning only fields of interest.
https://www.jfrog.com/confluence/display/RTF/Artifactory+Query+Language#ArtifactoryQueryLanguage-DisplayingSpecificFields
It doesn't work if I provide a list of fields, see below
Not Work - Bad request (400)
items.find(...).include("name", "repo")
Works
items.find(...).include("*")
Can anyone advise
Thanks, Jag
I suspect that the problem is related to encoding during the REST call, therefore I suggest to upload the query as a file Here is a working example:
Save the following query to file, lets call it aql.query
items.find
(
{
"repo": {"$match":"*"}
}
)
.include("name","repo")
Run the following curl command from the same directory that contains the aql.query file and don't forget to replace the templates in the command with your user name, password, host and port.
curl -X POST -uuser:password 'http://host:port/artifactory/api/search/aql' -Taql.query
In the result you will get:
{
"results" :
[
{
"repo" : "ext-snapshot-local",
"name" : "maven-metadata.xml"
},{
"repo" : "ext-snapshot-local",
"name" : "multi-3.0.0-20150705.195404-1.pom"
},{
.
.
.
}
],
"range" :
{
"start_pos" : 0,
"end_pos" : 46,
"total" : 46
}
}
As you can see that the result contains only the "item repo" and the "item name" fields.
Had the same issue. Spent quite a bit of time trying to figure this out. Couldn't find an answer online.
With a bad request(400), I printed the response text: "For permissions reasons AQL demands the following fields: repo, path and name."
This solution worked for me -
at a minimum: have repo, path, name.
ie... items.find(...).include("name", "repo", "path", "created_by")

Resources