Learn JSON superpowers with jq - automate GitLab Personal Access Token management
I've seen a question on the GitLab forum about managing Personal Access Tokens (PATs) with the API. While trying things, I had a peek into filtering the output and quickly getting access to values and IDs for automation.
This blog post covers the basics around listing PATs, working with jq
filters and lastly revoking PATs, practicing the filters even more :)
Requirements
- Export your full admin personal access token into an environment variable called
GITLAB_TOKEN
. The examples below reference this with setting the header to-H "PRIVATE-TOKEN: $GITLAB_TOKEN"
. - Persist this in your user's environment, e.g. with ZSH and the .env plugin.
List all your personal access tokens
$ curl -H "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.com/api/v4/personal_access_tokens
This returns raw JSON and is hard to read. jq
allows you to format the JSON body in a readable way. Learn more about jq and its usage on the official website.
On macOS, you can install jq
using Homebrew. Alternatively, use brew bundle.
$ brew install jq
Pretty print JSON with jq
Let's try retrieving the PATs again, and pipe the JSON body into jq
. In addition, we remove the curl stderr verbose output to /dev/null
. Note: I have modified the user_id
value for privacy reasons.
$ curl -H "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.com/api/v4/personal_access_tokens 2&>1 >/dev/null | jq
[
{
"id": 2992610,
"name": "ci-monitoring-webcast",
"revoked": false,
"created_at": "2020-08-24T09:30:21.088Z",
"scopes": [
"api",
"read_user",
"read_api",
"read_repository",
"read_registry"
],
"user_id": 1234567,
"active": true,
"expires_at": null
}
]
The JSON body contains all personal access tokens as an array with dictionary items. The more data is received the longer the list gets.
Filter JSON based on key/value
jq
provides its own query language for parsing JSON into objects and inspect its attributes. Let's mimic the PAT example above into a simple data structure:
$ vim pat.json
[
{
"id": 1,
"name": "pat1"
},
{
"id": 2,
"name": "pat2"
}
]
Cat this file and pipe the content into jq
. First, access all array elements:
$ cat pat.json | jq -c '.[]'
{"id":1,"name":"pat1"}
{"id":2,"name":"pat2"}
Add a filter with | select (...)
and specify the comparison condition. The dot indexer .
allows to access dictionary keys and their value. Try to filter .name
by the string value pat2
:
$ cat pat.json | jq -c '.[] | select (.name == "pat2")'
{"id":2,"name":"pat2"}
The syntax needs practice, repeat this with checking
- equality on
pat1
for.name
- print only the
.id
with the value of1
Filter with contains and regex
Sometimes you do not know the exact string. .name | contains(...)
provides checking if the given string is inside .name
.
$ cat pat.json | jq -c '.[] | select (.name | contains("2") )'
{"id":2,"name":"pat2"}
In order to use a regex, invoke the test()
function. Note: Backslashes for matching a number \d+
need to be escaped with an additional backslash, becoming \\d+
.
$ cat pat.json | jq -c '.[] | select (.name | test("^pat\\d+") )'
{"id":1,"name":"pat1"}
{"id":2,"name":"pat2"}
Real example with filtering GitLab PATs
Back to our example: Select the personal access token which name contains the string monitoring
.
$ curl -H "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.com/api/v4/personal_access_tokens 2&>1 >/dev/null | jq -c '.[] | select( .name | contains("monitoring"))'
{"id":9876543,"name":"ci-monitoring-webcast","revoked":false,"created_at":"2020-08-24T09:30:21.088Z","scopes":["api","read_user","read_api","read_repository","read_registry"],"user_id":1234567,"active":true,"expires_at":null}
This renders a raw JSON output again. Pretty print this with again piping to jq
💡
$ curl -H "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.com/api/v4/personal_access_tokens 2&>1 >/dev/null | jq -c '.[] | select( .name | contains("monitoring"))' | jq
{
"id": 9876543,
"name": "ci-monitoring-webcast",
"revoked": false,
"created_at": "2020-08-24T09:30:21.088Z",
"scopes": [
"api",
"read_user",
"read_api",
"read_repository",
"read_registry"
],
"user_id": 1234567,
"active": true,
"expires_at": null
}
The reason for going here was to finally select the value of the .id
index. This is the token ID which we want to revoke.
$ curl -H "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.com/api/v4/personal_access_tokens 2&>1 >/dev/null | jq -c '.[] | select( .name | contains("monitoring"))' | jq -c '.id'
9876543
For automation purposes, you can store the output in a shell variable to process in the next step, REVOKE_ID
as a example.
$ REVOKE_ID=$(curl -H "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.com/api/v4/personal_access_tokens 2&>1 >/dev/null | jq -c '.[] | select( .name | contains("monitoring"))' | jq -c '.id')
Revoke a GitLab Personal Access Token
Note that you cannot delete PATs, only revoke them. They will be hidden from the UI by default.
-X
or --request
defines DELETE
as request method. If not provided, this uses GET
by default. The URL path needs to add the ID as the last path string.
$ curl -X DELETE -H "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.com/api/v4/personal_access_tokens/$REVOKE_ID
You can check success with the HTTP response code 204
explained in the docs, using -w 204
.
Practice: List only revoked PATs
Inspect the attributes in the GitLab API docs. revoked
is of the boolean type for the jq
filter.
$ curl -H "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.com/api/v4/personal_access_tokens 2&>1 >/dev/null | jq -c '.[] | select( .revoked == true)' | jq
{
"id": 9876543,
"name": "ci-monitoring-webcast",
"revoked": true,
"created_at": "2020-08-24T09:30:21.088Z",
"scopes": [
"api",
"read_user",
"read_api",
"read_repository",
"read_registry"
],
"user_id": 1234567,
"active": false,
"expires_at": null
}
This works the other way around with .revoked == false
to only list active PATs.
Conclusion
While writing this blog post, I again learned great new things. Let me know on Twitter if the learning curve is the same for you, or what other cool things you learned :)
Tip for practicing: Download the JSON output from the API with -o pat.json
and use the methods above to learn more about jq
filters. This avoids pulling remote APIs too often.
$ curl -H "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.com/api/v4/personal_access_tokens 2&>1 >/dev/null -o pat.json
$ cat pat.json | jq