Mass Deletion of Github Actions Workflow Runs By Name and Date
Intro
Deleting Github Action workflows the old fashioned way can be painful. I for example had a repository which had old workflows that were no longer used as seen below. These old workflows were in an abundance, in both their name and how many times they were run and deleting them one by one just was not going to cut it.
My use case and previously made solutions
I knew the answer to this lied within creating a script using the GitHub API, more specifically the workflow runs API. I’ve used GitHub’s API briefly before, but only for testing and reading, not to create or delete.
My objective was clear, I wanted to either get rid of all workflows under a specific name, or to remove workflows before a certain date. In future, I would even want to add the functionality to select multiple workflows and delete en masse.
Before I went into possibly reinventing the wheel, I did a quick google and came across two articles to get me started: DJ Adam’s very handy script “mass-deletion-of-github-actions-workflow-runs” and then to fit my date use case Michael Heap’s post “Filter for dates before today with jq”
I would fully recommend following DJ’s post on the matter, it’s very easy to follow and provides a very good starting point for customising what you need from GitHub’s API. The following is a variation of his script.
Prerequisites
- GitHub API Authentication, preferably with a PAT.
- JQ (Json Processor), I’m using macOS so I installed JQ using brew
- The GitHub CLI
- If you want to test out your API authentication using GH CLI run this command:
gh api --method GET /octocat \ --header 'Accept: application/vnd.github+json' \ --header "X-GitHub-Api-Version: 2022-11-28"
- If you want to test out your API authentication using GH CLI run this command:
Creating the script
Viewing your worflows
Using Github’s API alone gives you a lot of data that isn’t necessary, DJ provides a neat solution in using jq
to filter down the parameters you want.
gh api /repos/<owner>/<repo>/actions/runs \
| jq -r '.workflow_runs[] | [.id, .conclusion, .name] | @tsv' \
| head -5
Which will neatly return:
Info
There’s built-in support for pagination with gh api, with the --paginate
switch.
The different parts of the jq call are as follows:
Part | Description |
---|---|
-r |
Tells jq to output raw values, rather than JSON structures |
.workflows_runs[] |
Process each of the entries in the workflow runs array |
[.id, .conclusion, .name] |
Show values of these three properties |
@tsv |
Convert everything into tab separated values |
Getting all workflow runs by name and date
Here DJ continues to use fzf to allow a user to select multiple workflows to delete in parallel. For my usecase, my primary goal was to completely rid of a specific workflow run, or runs before a certain date.
1while getopts ":n:d:" opt; do
2 case "$opt" in
3 n) select_name=$OPTARG ;;
4 d) select_date=$(date -u -j -f "%Y-%m-%d %H:%M:%S" "$OPTARG" +"%Y-%m-%dT%H:%M:%SZ") ;;
5 # GH CLI uses format: 2024-08-23T17:12:55Z
6 # Humans want to use format "2024-08-23 17:12:55"
7 esac
8done
I started the script by allowing command line arguments for a name
and a human usable date
. There is room in that script to only require the year
month
and day
for input, but I wanted to be specific and allow input of hour
, minute
and second
.
Filtering runs by name and date
Next was to add in a function to, in a more readable fashion, pipe our jq
script to the API call.
1jqscript() {
2
3 cat <<EOF
4 .workflow_runs[]
5 | select((.name | startswith("$select_name")) and (.created_at < "$select_date"))
6 | [
7 (.conclusion),
8 (.created_at),
9 .id,
10 .event,
11 .name
12 ]
13 | @tsv
14EOF
15
16}
Line 5 here being of note, startswith()
is used as if a user doesn’t want to filter by a specific name we can pre-set $select_name
to blank, where it will fetch all workflow runs.
Then following Michael’s advice, or in my case the inverse of his advice.
jq has built in support for ISO8601 format dates, so 2021-10-02 > 2021-10-02. — michaelheap 1
Deleting runs
Similar to how one can retrieve runs with the GH API using
/repos/<owner>/<repo>/actions/runs
one can delete by using:
DELETE /repos/<owner>/<repo>/actions/runs/<run_id>
Then it becomes a case of getting the selected runs, and passing them through a delete runs function:
1local run id result
2run=$1
3id="$(cut -f 3 <<< "$run")"
4gh api -X DELETE "/repos/$repo/actions/runs/$id"
5[[ $? = 0 ]] && result="OK!" || result="BAD"
6printf "%s\t%s\n" "$result" "$run")
The end result is this script here:
Feel free to comment on the gist and to use the script!
-
The above quote is excerpted from Michael’s post on Filter for dates before today with jq. ↩︎