1
1
import os
2
- import json
3
2
import asyncio
4
3
import aiohttp
5
4
from rich import print
6
5
from datetime import datetime
6
+ from pathlib import Path
7
7
import humanize
8
8
from itertools import count
9
9
10
- orgs = ["jupyter" , 'ipython' , 'jupyterhub' , 'jupyterlab' ]
10
+ orgs = [
11
+ "binder-examples" ,
12
+ "binderhub-ci-repos" ,
13
+ "ipython" ,
14
+ "jupyter" ,
15
+ "jupyter-book" ,
16
+ "jupyter-governance" ,
17
+ "jupyter-incubator" ,
18
+ "jupyter-server" ,
19
+ "jupyter-standards" ,
20
+ "jupyter-widgets" ,
21
+ "jupyterhub" ,
22
+ "jupyterlab" ,
23
+ "jupyter-xeus" ,
24
+ "jupytercon" ,
25
+ "voila-dashboards" ,
26
+ "voila-gallery" ,
27
+ ]
11
28
token = os .getenv ("GH_TOKEN" )
12
29
if not token :
13
30
print ("[red]Error: GH_TOKEN environment variable not set[/red]" )
14
31
exit (1 )
15
32
16
33
headers = {
17
- ' Authorization' : f' token { token } ' ,
18
- ' Accept' : ' application/vnd.github.v3+json'
34
+ " Authorization" : f" token { token } " ,
35
+ " Accept" : " application/vnd.github.v3+json" ,
19
36
}
20
37
21
- async def check_private_vulnerability_reporting (session : aiohttp .ClientSession , org : str , repo_name : str ) -> bool :
38
+
39
+ async def check_private_vulnerability_reporting (
40
+ session : aiohttp .ClientSession , org : str , repo_name : str
41
+ ) -> bool :
22
42
"""Check if private vulnerability reporting is enabled for a repository
23
-
43
+
24
44
Parameters
25
45
----------
26
46
session: aiohttp.ClientSession
@@ -34,17 +54,18 @@ async def check_private_vulnerability_reporting(session: aiohttp.ClientSession,
34
54
-------
35
55
bool: True if enabled, False otherwise
36
56
"""
37
- url = f' https://api.github.com/repos/{ org } /{ repo_name } /private-vulnerability-reporting'
38
-
57
+ url = f" https://api.github.com/repos/{ org } /{ repo_name } /private-vulnerability-reporting"
58
+
39
59
async with session .get (url , headers = headers ) as response :
40
60
if response .status == 200 :
41
61
data = await response .json ()
42
- return data .get (' enabled' , False )
62
+ return data .get (" enabled" , False )
43
63
return False
44
64
65
+
45
66
async def get_org_repos (session : aiohttp .ClientSession , org : str ) -> list [dict ]:
46
67
"""Get all repositories for an organization
47
-
68
+
48
69
Parameters
49
70
----------
50
71
session: aiohttp.ClientSession
@@ -57,33 +78,41 @@ async def get_org_repos(session: aiohttp.ClientSession, org: str) -> list[dict]:
57
78
list[dict]: The list of repositories
58
79
"""
59
80
repos = []
60
-
81
+
61
82
for page in count (1 ): # starts at 1 and counts up infinitely
62
- url = f' https://api.github.com/orgs/{ org } /repos?page={ page } &per_page=100'
83
+ url = f" https://api.github.com/orgs/{ org } /repos?page={ page } &per_page=100"
63
84
async with session .get (url , headers = headers ) as response :
64
85
if response .status != 200 :
65
86
print (f"[red]Error fetching repos: { response .status } [/red]" )
66
87
break
67
-
88
+
68
89
page_repos = await response .json ()
69
90
if not page_repos : # empty page means we've reached the end
70
91
break
71
-
92
+
72
93
repos .extend (page_repos )
73
-
94
+
74
95
return repos
75
96
97
+
76
98
async def main ():
99
+ ignores = Path ("psc_ignore.txt" ).read_text ().splitlines ()
77
100
async with aiohttp .ClientSession () as session :
78
101
# Check rate limit before making requests
79
- async with session .get ('https://api.github.com/rate_limit' , headers = headers ) as response :
102
+ async with session .get (
103
+ "https://api.github.com/rate_limit" , headers = headers
104
+ ) as response :
80
105
if response .status == 200 :
81
106
rate_data = await response .json ()
82
- remaining = rate_data ['resources' ]['core' ]['remaining' ]
83
- reset_time = datetime .fromtimestamp (rate_data ['resources' ]['core' ]['reset' ])
107
+ remaining = rate_data ["resources" ]["core" ]["remaining" ]
108
+ reset_time = datetime .fromtimestamp (
109
+ rate_data ["resources" ]["core" ]["reset" ]
110
+ )
84
111
reset_in = humanize .naturaltime (reset_time )
85
112
if remaining < 100 :
86
- print (f"[yellow]Warning: Rate limit is low! ({ remaining } remaining, full in { reset_in } )[/yellow]" )
113
+ print (
114
+ f"[yellow]Warning: Rate limit is low! ({ remaining } remaining, full in { reset_in } )[/yellow]"
115
+ )
87
116
if remaining < 10 :
88
117
print ("[red]Aborting due to very low rate limit[/red]" )
89
118
return
@@ -99,27 +128,46 @@ async def main():
99
128
for (org , _ ), org_repos in zip (org_tasks , org_results ):
100
129
for repo in org_repos :
101
130
repos .append ((org , repo ))
102
-
103
- for org , repo in sorted (repos , key = lambda x : x [1 ]['name' ]):
104
- repo_name = repo ['name' ]
105
-
131
+
132
+ for org , repo in sorted (repos , key = lambda x : x [1 ]["name" ]):
133
+ if f"{ org } /{ repo ['name' ]} " in ignores :
134
+ print (
135
+ f"[yellow]Ignoring { org } /{ repo ['name' ]} from ignore file[/yellow]"
136
+ )
137
+ continue
138
+ repo_name = repo ["name" ]
139
+
106
140
task = check_private_vulnerability_reporting (session , org , repo_name )
107
141
tasks .append ((repo , org , repo_name , task ))
108
-
109
- results = await asyncio .gather (* [task for _ ,_ ,_ , task in tasks ])
110
-
111
- for (repo , org , repo_name , _ ), has_vuln_reporting in sorted (zip (tasks , results ), key = lambda x : x [0 ][0 ]['pushed_at' ], reverse = True ):
112
- last_activity = repo ['pushed_at' ]
113
- last_activity_date = datetime .fromisoformat (last_activity ).strftime ("%Y-%m-%d" )
114
- last_activity_ago_human = humanize .naturaltime (datetime .now (datetime .fromisoformat (last_activity ).tzinfo ) - datetime .fromisoformat (last_activity ))
115
-
116
- if repo ['archived' ]:
117
- print (f"{ org + '/' + repo_name :<55} : [yellow]Archived { 'Enabled' if has_vuln_reporting else 'Disabled[/yellow]' } " )
118
- elif repo ['private' ]:
119
- print (f"{ org + '/' + repo_name :<55} : [yellow]Private { 'Enabled' if has_vuln_reporting else 'Disabled[/yellow]' } –– last activity: { last_activity_date } ({ last_activity_ago_human } )" )
142
+
143
+ results = await asyncio .gather (* [task for _ , _ , _ , task in tasks ])
144
+
145
+ for (repo , org , repo_name , _ ), has_vuln_reporting in sorted (
146
+ zip (tasks , results ), key = lambda x : x [0 ][0 ]["pushed_at" ], reverse = True
147
+ ):
148
+ last_activity = repo ["pushed_at" ]
149
+ last_activity_date = datetime .fromisoformat (last_activity ).strftime (
150
+ "%Y-%m-%d"
151
+ )
152
+ last_activity_ago_human = humanize .naturaltime (
153
+ datetime .now (datetime .fromisoformat (last_activity ).tzinfo )
154
+ - datetime .fromisoformat (last_activity )
155
+ )
156
+
157
+ if repo ["archived" ]:
158
+ print (
159
+ f"{ org + '/' + repo_name :<55} : [yellow]Archived { 'Enabled' if has_vuln_reporting else 'Disabled[/yellow]' } "
160
+ )
161
+ elif repo ["private" ]:
162
+ print (
163
+ f"{ org + '/' + repo_name :<55} : [yellow]Private { 'Enabled' if has_vuln_reporting else 'Disabled[/yellow]' } –– last activity: { last_activity_date } ({ last_activity_ago_human } )"
164
+ )
120
165
121
166
else :
122
- print (f"{ org + '/' + repo_name :<55} : { '[green]Enabled[/green]' if has_vuln_reporting else '[red]Disabled[/red]' } –– last activity: { last_activity_date } ({ last_activity_ago_human } )" )
167
+ print (
168
+ f"{ org + '/' + repo_name :<55} : { '[green]Enabled[/green]' if has_vuln_reporting else '[red]Disabled[/red]' } –– last activity: { last_activity_date } ({ last_activity_ago_human } )"
169
+ )
170
+
123
171
124
172
if __name__ == "__main__" :
125
- asyncio .run (main ())
173
+ asyncio .run (main ())
0 commit comments