Skip to content

Commit e81f51e

Browse files
committed
Add workflow runs deletion - scheduled and manual
1 parent 872fd87 commit e81f51e

File tree

2 files changed

+287
-0
lines changed

2 files changed

+287
-0
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
param(
2+
[Parameter(Mandatory)]
3+
[string] $repo,
4+
[Parameter(Mandatory)]
5+
[string] $token,
6+
[Parameter(Mandatory)]
7+
[string] $keepDays
8+
)
9+
10+
$baseUri = "https://api.github.com"
11+
$repoBaseUri = "$baseUri/repos/$repo"
12+
$actionsBaseUri = "$repoBaseUri/actions"
13+
$runsBaseUri = "$actionsBaseUri/runs"
14+
15+
$headers = @{
16+
Authorization = "Bearer $token"
17+
Accept = "application/vnd.github+json"
18+
}
19+
20+
function Get() {
21+
param(
22+
[string] $uri
23+
)
24+
return Invoke-RestMethod -Uri $uri -Headers $headers -Method Get
25+
}
26+
27+
function DeleteRuns() {
28+
param (
29+
[System.Collections.ArrayList] $runIds
30+
)
31+
32+
foreach ($id in $runIds) {
33+
Write-Host "Deleting run $id"
34+
$deleteUri = "$runsBaseUri/$id"
35+
Invoke-RestMethod -Uri $deleteUri -Headers $headers -Method Delete
36+
}
37+
}
38+
39+
#
40+
# Gets all workflow runs, finds among them the ones that meet deletion requirements
41+
# (inactive workflow, closed pull-request or older than required days)
42+
# and sorts them into several buckets - "inactive", "pull_request", "workflow_dispatch", "push"
43+
# and "schedule"
44+
#
45+
function GetWorkflowRunsForDeletion()
46+
{
47+
[OutputType([System.Collections.Generic.Dictionary[System.String, System.Collections.ArrayList]])]
48+
param (
49+
[System.Int32] $storeDays
50+
)
51+
52+
[System.Collections.Generic.HashSet[int]]$inactiveWorkflows = New-Object System.Collections.Generic.HashSet[int]
53+
54+
$allWorkflows = Invoke-RestMethod -Uri "$actionsBaseUri/workflows" -Headers $headers -Method Get
55+
56+
# number of workflows is less then number of runs
57+
# so more efficient to get them all and find inactive ones
58+
foreach ($wf in $allWorkflows.workflows) {
59+
if ($wf.state -ne "active") {
60+
$inactiveWorkflows.Add($wf.id) | Out-Null;
61+
}
62+
}
63+
64+
[System.Collections.Generic.Dictionary[System.String, System.Collections.ArrayList]]$result = New-Object System.Collections.Generic.Dictionary"[System.String, System.Collections.ArrayList]"
65+
66+
#keys of the dictionary equal to events of workflows
67+
$result["pull_request"] = New-Object System.Collections.ArrayList
68+
$result["workflow_dispatch"] = New-Object System.Collections.ArrayList
69+
$result["push"] = New-Object System.Collections.ArrayList
70+
$result["schedule"] = New-Object System.Collections.ArrayList
71+
#special bucket
72+
$result["inactive"] = New-Object System.Collections.ArrayList
73+
74+
75+
$page = 1
76+
$per_page = 100
77+
78+
do {
79+
$uri = "$runsBaseUri" + "?page=$page&per_page=$per_page"
80+
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get
81+
82+
if ($response.total_count -eq 0) {
83+
return $result
84+
}
85+
86+
[System.DateTime]$created = (Get-Date).AddDays(-$storeDays)
87+
88+
foreach ($run in $response.workflow_runs) {
89+
if ($result.ContainsKey($run.event) -eq $true) {
90+
# Doesn't matter how old the run is - if the workflow is inactive
91+
# put it for deletion
92+
if ($inactiveWorkflows.Contains($run.workflow_id)) {
93+
$result["inactive"].Add($run) | Out-Null
94+
}
95+
else {
96+
if ($run.event -eq "pull_request") {
97+
# for PRs we collect only closed ones
98+
if ($run.pull_requests.Count -eq 0) {
99+
$result[$run.event].Add($run) | Out-Null
100+
}
101+
}
102+
else {
103+
# for the rest, we apply date filter
104+
$created_at = [System.DateTime]::Parse($run.created_at)
105+
if ($created_at -lt $created) {
106+
$result[$run.event].Add($run) | Out-Null
107+
}
108+
}
109+
}
110+
}
111+
}
112+
113+
$page++
114+
} while ($response.workflow_runs.Count -eq $per_page)
115+
116+
return $result
117+
}
118+
119+
#
120+
# Deletes runs of inactive workflows (of pull_request, push, workflow_dispatch or schedule event)
121+
#
122+
function DeleteRunsOfInactiveWorkflows() {
123+
param (
124+
[System.Collections.Generic.Dictionary[System.String, System.Collections.ArrayList]] $groups
125+
)
126+
127+
[System.Collections.ArrayList]$runs = $groups["inactive"]
128+
129+
if ($runs.Count -eq 0) {
130+
Write-Host "No runs of inactive workflows. Skipping"
131+
return
132+
}
133+
134+
Write-Host "Deleting runs of inactive workflows"
135+
$run_ids = $runs | Select-Object -ExpandProperty id
136+
DeleteRuns $run_ids
137+
}
138+
139+
#
140+
# Deletes runs of closed pull-requests
141+
#
142+
function DeleteRunsForClosedPR() {
143+
param (
144+
[System.Collections.Generic.Dictionary[System.String, System.Collections.ArrayList]] $groups
145+
)
146+
147+
[System.Collections.ArrayList]$runs = $groups["pull_request"]
148+
149+
if ($runs.Count -eq 0) {
150+
Write-Host "No runs for closed pull requests. Skipping"
151+
return
152+
}
153+
154+
Write-Host "Deleting runs for closed pull requests"
155+
$run_ids = $runs | Select-Object -ExpandProperty id
156+
DeleteRuns $run_ids
157+
}
158+
159+
#
160+
# Deletes runs of 'workflow_dispatch' event marked for deletion
161+
#
162+
function DeleteOldDispatchedRuns() {
163+
param (
164+
[System.Collections.Generic.Dictionary[System.String, System.Collections.ArrayList]] $groups
165+
)
166+
167+
[System.Collections.ArrayList]$runs = $groups["workflow_dispatch"]
168+
169+
if ($runs.Count -eq 0) {
170+
Write-Host "No dispatched runs to delete. Skipping"
171+
return
172+
}
173+
174+
Write-Host "Deleting old runs that were dispatched"
175+
$run_ids = $runs | Select-Object -ExpandProperty id
176+
DeleteRuns $run_ids
177+
}
178+
179+
#
180+
# Deletes runs of 'push' event marked for deletion
181+
#
182+
function DeleteOldRunsOnPush() {
183+
param (
184+
[System.Collections.Generic.Dictionary[System.String, System.Collections.ArrayList]] $groups
185+
)
186+
187+
[System.Collections.ArrayList]$runs = $groups["push"]
188+
189+
if ($runs.Count -eq 0) {
190+
Write-Host "No runs triggered by push to delete. Skipping"
191+
return
192+
}
193+
194+
Write-Host "Deleting old runs triggered by push"
195+
$run_ids = $runs | Select-Object -ExpandProperty id
196+
DeleteRuns $run_ids
197+
}
198+
199+
#
200+
# Deletes runs of 'schedule' event marked for deletion
201+
#
202+
function DeleteOldScheduledRuns() {
203+
param (
204+
[System.Collections.Generic.Dictionary[System.String, System.Collections.ArrayList]] $groups
205+
)
206+
207+
[System.Collections.ArrayList]$runs = $groups["schedule"]
208+
209+
if ($runs.Count -eq 0) {
210+
Write-Host "No scheduled runs to delete. Skipping"
211+
return
212+
}
213+
214+
Write-Host "Deleting old runs triggered on schedule"
215+
$run_ids = $runs | Select-Object -ExpandProperty id
216+
DeleteRuns $run_ids
217+
}
218+
219+
Write-Host "Collecting runs for closed PRs, inactive workflow runs, and other runs older than $keepDays days"
220+
221+
$preserveDays = [System.Int32]::Parse($keepDays)
222+
223+
$runsForDeletion = GetWorkflowRunsForDeletion $preserveDays
224+
225+
if ($runsForDeletion.ToString() -eq "System.Object[]")
226+
{
227+
# just in case somebody screws up method and forgot to apply out-null to some operation
228+
# and results of method became array of objects
229+
$runsForDeletion = $runsForDeletion[$runsForDeletion.Length - 1];
230+
}
231+
232+
DeleteRunsOfInactiveWorkflows $runsForDeletion
233+
234+
DeleteRunsForClosedPR $runsForDeletion
235+
236+
DeleteOldDispatchedRuns $runsForDeletion
237+
238+
DeleteOldRunsOnPush $runsForDeletion
239+
240+
DeleteOldScheduledRuns $runsForDeletion
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Runs at 01:00 every tuesday and deletes workflow runs for closed pull requests,
2+
# inactive workflows' runs or runs older than defined days.
3+
# Can also be run manually
4+
5+
name: "‼Delete workflow runs‼"
6+
run-name: Delete workflow run on ${{ github.event_name }}
7+
8+
on:
9+
schedule:
10+
- cron: "0 1 * * 2"
11+
workflow_dispatch:
12+
inputs:
13+
keepDays:
14+
description: 'Keep history existing runs of last X days'
15+
required: true
16+
default: 6
17+
type: number
18+
19+
env:
20+
KEEP_DAYS_DEFAULT: '6' # to not let auto-cleanup delete runs during weekend
21+
22+
permissions:
23+
actions: write
24+
contents: read
25+
26+
jobs:
27+
clear:
28+
if: github.refs == 'refs/heads/master'
29+
runs-on: ubuntu-22.04
30+
timeout-minutes: 10
31+
32+
steps:
33+
- name: Get cleanup scripts
34+
uses: actions/checkout@v4
35+
with:
36+
sparse-checkout: |
37+
.github/scripts
38+
39+
- name: Run workflow history manual cleanup
40+
if: ${{ github.event_name == 'workflow_dispatch' }}
41+
run: .github/scripts/delete_workflow_runs.ps1 -repo ${{ github.repository }} -token ${{ secrets.GITHUB_TOKEN }} -keepDays ${{ inputs.keepDays }}
42+
shell: pwsh
43+
44+
- name: Run workflow history auto cleanup
45+
if: ${{ github.event_name == 'schedule' }}
46+
run: .github/scripts/delete_workflow_runs.ps1 -repo ${{ github.repository }} -token ${{ secrets.GITHUB_TOKEN }} -keepDays ${{ env.KEEP_DAYS_DEFAULT }}
47+
shell: pwsh

0 commit comments

Comments
 (0)