Last active
September 19, 2021 16:24
-
-
Save simonbromberg/abb1e6dd09247760e7157ef098bb9840 to your computer and use it in GitHub Desktop.
Bulk deletion of GitHub Action workflow runs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import PlaygroundSupport | |
import UIKit | |
let baseURL = "https://api.github.com/repos/my_repo_owner/my_repo/actions/runs" | |
let auth = ["Authorization": "Token my_token"] | |
enum ResponseError: Error { | |
case invalidURL | |
case httpError(Error) | |
case badResponse(HTTPURLResponse) | |
case decodingFailure(Error) | |
case unknown | |
} | |
let semaphore = DispatchSemaphore(value: 1) | |
let dispatchGroup = DispatchGroup() | |
getAllRuns { runs in | |
print(runs.count) | |
runs.enumerated().forEach { i, run in | |
semaphore.wait() | |
print("Deleting \(i) of \(runs.count)…") | |
run.delete() { _ in | |
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { | |
semaphore.signal() | |
} | |
} | |
} | |
PlaygroundPage.current.finishExecution() | |
} | |
func getAllRuns(completion: ((_ runs: [Run]) -> Void)?) { | |
getRuns(page: 1) { result in | |
switch result { | |
case let .success(response): | |
let total = response.totalCount | |
let pages = 1 + total / 100 | |
var runs = response.targetWorkflowRuns | |
var page = 2 | |
while page <= pages { | |
dispatchGroup.enter() | |
getRuns(page: page) { result in | |
if case let .success(response) = result { | |
runs += response.targetWorkflowRuns | |
} | |
dispatchGroup.leave() | |
} | |
page += 1 | |
} | |
dispatchGroup.notify(queue: DispatchQueue.global()) { | |
completion?(runs) | |
} | |
case let .failure(error): | |
print(error) | |
completion?([]) | |
} | |
} | |
} | |
func getRuns(page: Int, completion: ((_ response: Result<Response, ResponseError>) -> Void)?) { | |
let urlSession = URLSession(configuration: .default) | |
var dataTask: URLSessionDataTask? | |
dataTask?.cancel() | |
if var urlComponents = URLComponents(string: baseURL) { | |
urlComponents.queryItems = [ | |
.init(name: "page", value: "\(page)"), | |
.init(name: "per_page", value: "100"), | |
] | |
guard let url = urlComponents.url else { | |
print("Invalid URL") | |
completion?(.failure(.invalidURL)) | |
return | |
} | |
var request = URLRequest(url: url) | |
request.httpMethod = "GET" | |
request.allHTTPHeaderFields = auth | |
dataTask = urlSession.dataTask(with: request) { data, response, error in | |
defer { | |
dataTask = nil | |
} | |
if let error = error { | |
print(error) | |
completion?(.failure(.httpError(error))) | |
} else if let response = response as? HTTPURLResponse, | |
let data = data { | |
guard response.statusCode == 200 else { | |
completion?(.failure(.badResponse(response))) | |
return | |
} | |
let decoder = JSONDecoder() | |
decoder.keyDecodingStrategy = .convertFromSnakeCase | |
do { | |
let decoded = try decoder.decode(Response.self, from: data) | |
completion?(.success(decoded)) | |
} catch { | |
completion?(.failure(.decodingFailure(error))) | |
} | |
} | |
} | |
dataTask?.resume() | |
} | |
} | |
struct Response: Decodable { | |
let totalCount: Int | |
let workflowRuns: [Run] | |
} | |
extension Response { | |
var targetWorkflowRuns: [Run] { | |
workflowRuns.filter(\.isTargetWorflow) | |
} | |
} | |
struct Run: Decodable { | |
let id: Int | |
let workflowId: Int | |
let name: String | |
} | |
let targetWorkflowId = 0 // replace with target workflow id | |
extension Run { | |
var isTargetWorflow: Bool { | |
workflowId == targetWorkflowId | |
} | |
func delete(completion: ((Result<Void, ResponseError>) -> Void)?) { | |
let urlSession = URLSession(configuration: .default) | |
var dataTask: URLSessionDataTask? | |
dataTask?.cancel() | |
guard let urlComponents = URLComponents(string: "\(baseURL)/\(id)"), | |
let url = urlComponents.url | |
else { | |
print("Invalid url") | |
completion?(.failure(.invalidURL)) | |
return | |
} | |
var request = URLRequest(url: url) | |
request.httpMethod = "DELETE" | |
request.allHTTPHeaderFields = auth | |
dataTask = urlSession.dataTask(with: request) { _, response, error in | |
defer { | |
dataTask?.cancel() | |
} | |
if let error = error { | |
print(error) | |
completion?(.failure(.httpError(error))) | |
} else if let response = response as? HTTPURLResponse { | |
guard response.statusCode == 204 else { | |
completion?(.failure(.badResponse(response))) | |
print("Bad response \(response.statusCode)") | |
return | |
} | |
print("Success!") | |
completion?(.success(())) | |
} else { | |
print("here") | |
completion?(.failure(.unknown)) | |
} | |
} | |
dataTask?.resume() | |
} | |
} |
Author
simonbromberg
commented
Sep 19, 2021
- https://docs.github.com/en/rest/reference/actions#list-workflow-runs-for-a-repository
- https://docs.github.com/en/rest/reference/actions#delete-a-workflow-run
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment