Skip to content

Commit 348e763

Browse files
authored
Merge pull request #13 from Hasenpfote/improve-gh-actions
Improve GitHub Actions
2 parents e98db8e + 5689a1a commit 348e763

File tree

12 files changed

+323
-9
lines changed

12 files changed

+323
-9
lines changed

.github/workflows/codecov_upload.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Upload coverage reports to Codecov
1+
name: '📄 Upload coverage reports to Codecov'
22

33
on:
44
workflow_call:
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
name: '🧰 Maint: Delete caches'
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
validity-days:
7+
description: 'Validity days'
8+
default: '7'
9+
type: choice
10+
options: ['0', '1', '2', '3', '4', '5', '6', '7']
11+
12+
key-pattern:
13+
description: 'Regular expression to match cache key'
14+
required: false
15+
default: ''
16+
type: string
17+
18+
dry-run:
19+
required: false
20+
default: true
21+
type: boolean
22+
23+
jobs:
24+
main:
25+
permissions:
26+
actions: write
27+
28+
runs-on: ubuntu-latest
29+
steps:
30+
- name: Delete caches
31+
uses: actions/github-script@v6
32+
with:
33+
script: |
34+
core.info('validity-days: ${{ inputs.validity-days }}');
35+
core.info('key-pattern: ${{ inputs.key-pattern }}');
36+
core.info('dry-run: ${{ inputs.dry-run }}');
37+
38+
let deleteActionsCacheById;
39+
if (${{ inputs.dry-run }} == true) {
40+
core.warning('Running in dry-run mode.');
41+
deleteActionsCacheById = (owner, repo, cache_id) => undefined;
42+
} else {
43+
deleteActionsCacheById = github.rest.actions.deleteActionsCacheById;
44+
}
45+
46+
const validity_days = parseInt('${{ inputs.validity-days }}', 10);
47+
const re = new RegExp('${{ inputs.key-pattern }}');
48+
const current_date = new Date();
49+
const per_page = 30;
50+
51+
async function numCaches() {
52+
let total_count = 0
53+
try {
54+
const caches = await github.rest.actions.getActionsCacheList({
55+
owner: context.repo.owner,
56+
repo: context.repo.repo,
57+
per_page: 1,
58+
});
59+
total_count = caches.data.total_count;
60+
} catch (e) {
61+
}
62+
return total_count;
63+
}
64+
65+
async function validCacheIds(num_caches) {
66+
const max_pages = Math.ceil(num_caches / per_page);
67+
let ids = []
68+
try {
69+
let num_pages = 1;
70+
do {
71+
const caches = await github.rest.actions.getActionsCacheList({
72+
owner: context.repo.owner,
73+
repo: context.repo.repo,
74+
per_page: per_page,
75+
page: num_pages,
76+
});
77+
for (const cache of caches.data.actions_caches) {
78+
const has_matched = re.test(cache.key);
79+
if (!has_matched) {
80+
continue;
81+
}
82+
83+
const expiry_date = new Date(cache.last_accessed_at);
84+
expiry_date.setDate(expiry_date.getDate() + validity_days);
85+
const has_expired = current_date.getTime() > expiry_date.getTime();
86+
if (!has_expired) {
87+
continue;
88+
}
89+
90+
ids.push(cache.id);
91+
}
92+
} while(num_pages++ < max_pages);
93+
} catch (e) {
94+
}
95+
return ids;
96+
}
97+
98+
let num_deleted_caches = 0;
99+
const num_caches = await numCaches();
100+
if (!num_caches) {
101+
core.info('No caches.');
102+
return;
103+
}
104+
105+
const cache_ids = await validCacheIds(num_caches);
106+
if (!cache_ids.length) {
107+
core.info('Nothing matches the given conditions.');
108+
return;
109+
}
110+
111+
for (const cache_id of cache_ids) {
112+
deleteActionsCacheById({
113+
owner: context.repo.owner,
114+
repo: context.repo.repo,
115+
cache_id: cache_id,
116+
});
117+
num_deleted_caches++;
118+
}
119+
core.notice(`${num_deleted_caches}/${num_caches} caches have been deleted.`);
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
name: '🧰 Maint: Delete workflow runs'
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
validity-days:
7+
description: 'Validity days'
8+
default: '7'
9+
type: choice
10+
options: ['0', '1', '2', '3', '4', '5', '6', '7']
11+
12+
name-pattern:
13+
description: 'Regular expression to match workflow name'
14+
required: false
15+
default: ''
16+
type: string
17+
18+
dry-run:
19+
required: false
20+
default: true
21+
type: boolean
22+
23+
jobs:
24+
main:
25+
permissions:
26+
actions: write
27+
28+
runs-on: ubuntu-latest
29+
steps:
30+
- name: Delete workflow runs
31+
uses: actions/github-script@v6
32+
with:
33+
script: |
34+
core.info('validity-days: ${{ inputs.validity-days }}');
35+
core.info('name-pattern: ${{ inputs.name-pattern }}');
36+
core.info('dry-run: ${{ inputs.dry-run }}');
37+
38+
let deleteWorkflowRun;
39+
if (${{ inputs.dry-run }} == true) {
40+
core.warning('Running in dry-run mode.');
41+
deleteWorkflowRun = (owner, repo, run_id) => undefined;
42+
} else {
43+
deleteWorkflowRun = github.rest.actions.deleteWorkflowRun;
44+
}
45+
46+
const validity_days = parseInt('${{ inputs.validity-days }}', 10);
47+
const re = new RegExp('${{ inputs.name-pattern }}');
48+
const current_date = new Date();
49+
const per_page = 30;
50+
51+
async function numWorkflowRuns(workflow_id) {
52+
let total_count = 0
53+
try {
54+
const wf_runs = await github.rest.actions.listWorkflowRuns({
55+
owner: context.repo.owner,
56+
repo: context.repo.repo,
57+
workflow_id: workflow_id,
58+
per_page: 1,
59+
});
60+
total_count = wf_runs.data.total_count;
61+
} catch (e) {
62+
}
63+
return total_count;
64+
}
65+
66+
async function validWorkflowRunIds(workflow_id, num_wf_runs) {
67+
const max_pages = Math.ceil(num_wf_runs / per_page);
68+
let ids = []
69+
try {
70+
let num_pages = 1;
71+
do {
72+
const wf_runs = await github.rest.actions.listWorkflowRuns({
73+
owner: context.repo.owner,
74+
repo: context.repo.repo,
75+
workflow_id: workflow_id,
76+
per_page: per_page,
77+
page: num_pages,
78+
});
79+
for (const wf_run of wf_runs.data.workflow_runs) {
80+
if (wf_run.status != 'completed') {
81+
continue;
82+
}
83+
84+
const expiry_date = new Date(wf_run.updated_at);
85+
expiry_date.setDate(expiry_date.getDate() + validity_days);
86+
const has_expired = current_date.getTime() > expiry_date.getTime();
87+
if (!has_expired) {
88+
continue;
89+
}
90+
91+
ids.push(wf_run.id);
92+
}
93+
} while(num_pages++ < max_pages);
94+
} catch (e) {
95+
}
96+
return ids;
97+
}
98+
99+
const wfs = await github.rest.actions.listRepoWorkflows({
100+
owner: context.repo.owner,
101+
repo: context.repo.repo,
102+
});
103+
for (const wf of wfs.data.workflows) {
104+
const basename = wf.path.split('/').reverse()[0];
105+
const has_matched = re.test(basename);
106+
if (!has_matched) {
107+
continue;
108+
}
109+
110+
let num_deleted_wf_runs = 0;
111+
const num_wf_runs = await numWorkflowRuns(wf.id);
112+
if (!num_wf_runs) {
113+
core.info(`[${basename}] This workflow has no runs yet.`);
114+
continue;
115+
}
116+
117+
const wf_run_ids = await validWorkflowRunIds(wf.id, num_wf_runs);
118+
if (!wf_run_ids.length) {
119+
core.info(`[${basename}] Nothing matches the given conditions.`);
120+
continue;
121+
}
122+
123+
for (const wf_run_id of wf_run_ids) {
124+
deleteWorkflowRun({
125+
owner: context.repo.owner,
126+
repo: context.repo.repo,
127+
run_id: wf_run_id,
128+
});
129+
num_deleted_wf_runs++;
130+
}
131+
core.notice(`[${basename}] ${num_deleted_wf_runs}/${num_wf_runs} workflow runs have been deleted.`);
132+
}

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Workflow for deploying static content to GitHub Pages
2-
name: Deploy static content to Pages
2+
name: '📚 Publish docs via GitHub Pages'
33

44
# build the documentation whenever there are new commits on main
55
on:

.github/workflows/issue_bot.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: issue-bot
1+
name: '🤖 Bot: Issues'
22

33
on:
44
issues:

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Lint
1+
name: '🔬 Lint'
22

33
on:
44
workflow_dispatch:

.github/workflows/pr_bot.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: pr-bot
1+
name: '🤖 Bot: PRs'
22

33
on:
44
pull_request_target:

.github/workflows/py_lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: py-lint
1+
name: '🔬 Lint: py'
22

33
on:
44
workflow_call:

.github/workflows/py_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: py-test
1+
name: '🧪 Test: py'
22

33
on:
44
workflow_call:
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: '🧰 Maint: Report rate limits'
2+
3+
on:
4+
workflow_dispatch:
5+
6+
jobs:
7+
main:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Dump rate limit
11+
uses: actions/github-script@v6
12+
with:
13+
script: |
14+
const indicator = (function () {
15+
const max_steps = 5;
16+
const threshold_to_emoji = new Map([
17+
[25, '🟥'], [50, '🟨'], [100, '🟩'],
18+
]);
19+
const scale = max_steps / 100;
20+
21+
return function (value) {
22+
const arr = new Array(max_steps).fill('🟫');
23+
if (value <= 0) return arr.join('');
24+
25+
let emoji;
26+
for (let entry of threshold_to_emoji) {
27+
if (value <= entry[0]) {
28+
emoji = entry[1];
29+
break;
30+
}
31+
}
32+
return arr.fill(emoji, 0, Math.ceil(value * scale)).join('');
33+
}
34+
})();
35+
36+
const current_date = new Date();
37+
const rate_limit = await github.request('GET /rate_limit', {});
38+
39+
const table = [];
40+
const header = Object.keys(rate_limit.data.rate).map(v => ({data: v, header: true}));
41+
header.push({data: '', header: true});
42+
header.push({data: 'Time until reset<br>in minutes', header: true});
43+
header.push({data: '', header: true});
44+
header.unshift({data: 'resource', header: true});
45+
table.push(header);
46+
47+
for (let [key, value] of Object.entries(rate_limit.data.resources).sort()) {
48+
const resource = Object.values(value).map(v => v.toString());
49+
const reset = new Date(value.reset * 1000);
50+
const time_left = Math.floor((reset - current_date) / 60000);
51+
resource.push(reset.toISOString());
52+
resource.push(value.used > 0 ? time_left.toString() : '');
53+
resource.push(indicator((value.remaining / value.limit) * 100));
54+
resource.unshift(key);
55+
table.push(resource);
56+
}
57+
58+
await core.summary
59+
.addHeading('Current rate limit status')
60+
.addTable(table)
61+
.addEOL()
62+
.addRaw('Created at ' + new Date().toISOString())
63+
.write()

0 commit comments

Comments
 (0)