Skip to content

Instantly share code, notes, and snippets.

@dgamanenko
Forked from magnetikonline/README.md
Created March 25, 2022 09:22
Show Gist options
  • Save dgamanenko/23d58627763f6192cfa92c08d6adaa0e to your computer and use it in GitHub Desktop.
Save dgamanenko/23d58627763f6192cfa92c08d6adaa0e to your computer and use it in GitHub Desktop.
Cleanup legacy GitHub Actions workflow runs.

Cleanup legacy GitHub Actions workflow runs

Python utility to delete in bulk all GitHub Actions runs for a given workflow, either current or legacy/since removed. The GitHub UI currently allows removal of individual workflow runs, but this becomes tedious with a bulk of previous runs.

Usage

Create a Personal access token with workflow scope:

image

Execute the script against a target GitHub repository and workflow ID:

export AUTH_TOKEN="GITHUB_PERSONAL_ACCESS_TOKEN"
./remove --repository-name OWNER_NAME/REPOSITORY_NAME --workflow-id WORKFLOW_ID

where:

  • OWNER_NAME is either a GitHub username or organisation name.
  • WORKFLOW_ID is the YAML filename that defined the workflow.

example:

export AUTH_TOKEN="GITHUB_PERSONAL_ACCESS_TOKEN"
./remove --repository-name magnetikonline/my-test-repo --workflow-id test.yaml

Once executed, script will retreive the IDs of each run associated to the given workflow ID and then delete each one in turn.

Reference

#!/usr/bin/env python3
import argparse
import json
import os
import urllib.parse
import urllib.request
API_BASE_URL = "https://api.github.com"
REQUEST_ACCEPT_VERSION = "application/vnd.github.v3+json"
REQUEST_USER_AGENT = "magnetikonline/remove-workflow-run"
def github_request(
auth_token, path, method=None, parameter_collection=None, parse_response=True
):
# build base request URL/headers
request_url = f"{API_BASE_URL}/{path}"
header_collection = {
"Accept": REQUEST_ACCEPT_VERSION,
"Authorization": f"token {auth_token}",
"User-Agent": REQUEST_USER_AGENT,
}
if method is None:
# GET method
if parameter_collection is not None:
request_url = (
f"{request_url}?{urllib.parse.urlencode(parameter_collection)}"
)
request = urllib.request.Request(headers=header_collection, url=request_url)
else:
# POST/PATCH/PUT/DELETE method
request = urllib.request.Request(
headers=header_collection, method=method, url=request_url
)
response = urllib.request.urlopen(request)
response_data = {}
if parse_response:
response_data = json.load(response)
response.close()
return response_data
def workflow_run_list(auth_token, owner_repo_name, workflow_id):
request_page = 1
while True:
data = github_request(
auth_token,
f"repos/{owner_repo_name}/actions/workflows/{urllib.parse.quote(workflow_id)}/runs",
parameter_collection={"page": request_page},
)
run_list = data["workflow_runs"]
if len(run_list) < 1:
# no more items
break
for item in run_list:
yield item["id"]
# move to next page
request_page += 1
def workflow_run_delete(auth_token, owner_repo_name, run_id):
github_request(
auth_token,
f"repos/{owner_repo_name}/actions/runs/{run_id}",
method="DELETE",
parse_response=False,
)
def main():
# fetch GitHub access token
auth_token = os.environ["AUTH_TOKEN"]
# fetch requested repository and workflow ID to remove prior runs from
parser = argparse.ArgumentParser()
parser.add_argument("--repository-name", required=True)
parser.add_argument("--workflow-id", required=True)
arg_list = parser.parse_args()
# fetch run id list from repository workflow
run_id_list = list(
workflow_run_list(auth_token, arg_list.repository_name, arg_list.workflow_id)
)
for run_id in run_id_list:
print(f"Deleting run ID: {run_id}")
workflow_run_delete(auth_token, arg_list.repository_name, run_id)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment