Skip to content

Instantly share code, notes, and snippets.

@simonbromberg
Last active September 19, 2021 16:24
Show Gist options
  • Save simonbromberg/abb1e6dd09247760e7157ef098bb9840 to your computer and use it in GitHub Desktop.
Save simonbromberg/abb1e6dd09247760e7157ef098bb9840 to your computer and use it in GitHub Desktop.
Bulk deletion of GitHub Action workflow runs
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()
}
}