Skip to content

Commit aa449ef

Browse files
authored
sync_labels_actor_authorized initial (#8)
1 parent dbad117 commit aa449ef

File tree

2 files changed

+110
-30
lines changed

2 files changed

+110
-30
lines changed

.github/sync_labels.py

+91-28
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import os
2020
import sys
21-
from logging import info, warning, debug, getLogger, INFO, DEBUG
21+
from logging import info, warning, debug, getLogger, INFO, DEBUG, WARNING
2222
from json import loads
2323
from enum import Enum
2424
from datetime import datetime, timedelta
@@ -152,13 +152,11 @@ def rest_api(self, path_args, method=None, query=''):
152152
r"""
153153
Return data obtained from ``gh`` command ``api``.
154154
"""
155-
s = self._url.split('/')
156-
owner = s[3]
157-
repo = s[4]
158155
meth = '-X GET'
159156
if method:
160157
meth='-X %s' % method
161-
cmd = 'gh api %s -H \"Accept: application/vnd.github+json\" /repos/%s/%s/%s %s' % (meth, owner, repo, path_args, query)
158+
cmd = 'gh api %s -H \"Accept: application/vnd.github+json\" %s %s' % (meth, path_args, query)
159+
debug('Execute command: %s' % cmd)
162160
if method:
163161
return check_output(cmd, shell=True)
164162
return loads(check_output(cmd, shell=True))
@@ -171,11 +169,12 @@ def view(self, key):
171169
if self._pr:
172170
issue = 'pr'
173171
cmd = 'gh %s view %s --json %s' % (issue, self._url, key)
172+
debug('Execute command: %s' % cmd)
174173
return loads(check_output(cmd, shell=True))[key]
175174

176175
def is_open(self):
177176
r"""
178-
Return if the issue res. PR is open.
177+
Return ``True`` if the issue res. PR is open.
179178
"""
180179
if self._open is not None:
181180
return self._open
@@ -188,7 +187,7 @@ def is_open(self):
188187

189188
def is_draft(self):
190189
r"""
191-
Return if the PR is a draft.
190+
Return ``True`` if the PR is a draft.
192191
"""
193192
if self._draft is not None:
194193
return self._draft
@@ -199,22 +198,55 @@ def is_draft(self):
199198
info('Issue %s is draft %s' % (self._issue, self._draft))
200199
return self._draft
201200

201+
def is_auth_team_member(self, login):
202+
r"""
203+
Return ``True`` if the user with given login belongs to an authorized
204+
team.
205+
"""
206+
def verify_membership(team):
207+
path_args = '/orgs/sagemath/teams/%s/memberships/%s' % (team, login)
208+
try:
209+
res = self.rest_api(path_args)
210+
if res['state'] == 'active' and res['role'] == 'member':
211+
info('User %s is a member of %s' % (login, team))
212+
return True
213+
except CalledProcessError:
214+
pass
215+
216+
info('User %s is not a member of %s' % (login, team))
217+
return False
218+
219+
# check for the Triage team
220+
if verify_membership('triage'):
221+
return True
222+
223+
return False
224+
225+
def actor_authorized(self):
226+
r"""
227+
Return ``True`` if the actor belongs to an authorized team.
228+
"""
229+
return self.is_auth_team_member(self._actor)
230+
202231
def clean_warnings(self):
203232
r"""
204233
Remove all warnings that have been posted by ``GhLabelSynchronizer``
205234
more than ``warning_lifetime`` ago.
206235
"""
207236
warning_lifetime = timedelta(minutes=5)
208-
time_frame = timedelta(hours=12) # timedelta to search for comments
237+
time_frame = timedelta(minutes=730) # timedelta to search for comments including 10 minutes overlap with cron-cycle
209238
per_page = 100
210239
today = datetime.today()
211240
since = today - time_frame
212241
query = '-F per_page=%s -F page={} -f since=%s' % (per_page, since.strftime(datetime_format))
213-
path = 'issues/comments'
242+
s = self._url.split('/')
243+
owner = s[3]
244+
repo = s[4]
245+
path_args = '/repos/%s/%s/issues/comments' % (owner, repo)
214246
page = 1
215247
comments = []
216248
while True:
217-
comments_page = self.rest_api(path, query=query.format(page))
249+
comments_page = self.rest_api(path_args, query=query.format(page))
218250
comments += comments_page
219251
if len(comments_page) < per_page:
220252
break
@@ -236,7 +268,7 @@ def clean_warnings(self):
236268
debug('github-actions %s %s is %s old' % (self._warning_prefix, comment_id, lifetime))
237269
if lifetime > warning_lifetime:
238270
try:
239-
self.rest_api('%s/%s' % (path, comment_id), method='DELETE')
271+
self.rest_api('%s/%s' % (path_args, comment_id), method='DELETE')
240272
info('Comment %s on issue %s deleted' % (comment_id, issue))
241273
except CalledProcessError:
242274
# the comment may have been deleted by a bot running in parallel
@@ -740,7 +772,7 @@ def run(self, action, label=None, rev_state=None):
740772
if action is Action.submitted:
741773
rev_state = RevState(rev_state)
742774
if rev_state is RevState.approved:
743-
if self.positive_review_valid():
775+
if self.actor_authorized() and self.positive_review_valid():
744776
self.select_label(State.positive_review)
745777

746778
if rev_state is RevState.changes_requested:
@@ -799,44 +831,75 @@ def run_tests(self):
799831
###############################################################################
800832
# Main
801833
###############################################################################
834+
last_arg = None
835+
run_tests = False
836+
default_actor = 'sagetrac-github-bot'
802837
cmdline_args = sys.argv[1:]
803838
num_args = len(cmdline_args)
804839

805-
# getLogger().setLevel(INFO)
806-
getLogger().setLevel(DEBUG)
840+
if num_args:
841+
last_arg = cmdline_args[num_args-1]
842+
843+
if last_arg in ('-t', '--test'):
844+
getLogger().setLevel(DEBUG)
845+
cmdline_args.pop()
846+
run_tests = True
847+
elif last_arg in ('-d', '--debug'):
848+
getLogger().setLevel(DEBUG)
849+
cmdline_args.pop()
850+
elif last_arg in ('-i', '--info'):
851+
getLogger().setLevel(INFO)
852+
cmdline_args.pop()
853+
elif last_arg in ('-w', '--warning'):
854+
getLogger().setLevel(INFO)
855+
info('cmdline_args (%s) %s' % (num_args, cmdline_args))
856+
getLogger().setLevel(WARNING)
857+
cmdline_args.pop()
858+
else:
859+
getLogger().setLevel(DEBUG)
807860

861+
num_args = len(cmdline_args)
808862
info('cmdline_args (%s) %s' % (num_args, cmdline_args))
809863

810-
if num_args == 5:
811-
action, url, actor, label, rev_state = cmdline_args
812-
action = Action(action)
864+
if run_tests and num_args in (1,2):
865+
if num_args == 2:
866+
url, actor = cmdline_args
867+
else:
868+
url, = cmdline_args
869+
actor = default_actor
813870

814-
info('action: %s' % action)
815871
info('url: %s' % url)
816872
info('actor: %s' % actor)
817-
info('label: %s' % label)
818-
info('rev_state: %s' % rev_state)
819873

820874
gh = GhLabelSynchronizer(url, actor)
821-
gh.run(action, label=label, rev_state=rev_state)
875+
gh.run_tests()
822876

823-
elif num_args == 2:
824-
url, actor = cmdline_args
877+
elif num_args == 5:
878+
action, url, actor, label, rev_state = cmdline_args
879+
action = Action(action)
825880

881+
info('action: %s' % action)
826882
info('url: %s' % url)
827883
info('actor: %s' % actor)
884+
info('label: %s' % label)
885+
info('rev_state: %s' % rev_state)
828886

829887
gh = GhLabelSynchronizer(url, actor)
830-
gh.run_tests()
888+
gh.run(action, label=label, rev_state=rev_state)
831889

832890
elif num_args == 1:
833891
url, = cmdline_args
834892

835893
info('url: %s' % url)
836894

837-
gh = GhLabelSynchronizer(url, 'sagetrac-github-bot')
895+
gh = GhLabelSynchronizer(url, default_actor)
838896

839897
else:
840-
print('Need 5 arguments: action, url, actor, label, rev_state' )
841-
print('Running tests is possible with 2 arguments: url, actor' )
842-
print('Cleaning warning comments is possible with 1 argument: url' )
898+
print('Need 5 arguments to synchronize: action, url, actor, label, rev_state')
899+
print('Need 1 argument to clean warning comments: url')
900+
print('Need 1 argument to run tests: url')
901+
print('The following options may be appended:')
902+
print(' -t --test to run the test suite')
903+
print(' -i --info to set the log-level to INFO')
904+
print(' -d --debug to set the log-level to DEBUG (default)')
905+
print(' -w --warning to set the log-level to WARNING')

.github/workflows/sync_labels.yml

+19-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ on:
1313
types: [submitted]
1414
pull_request_target:
1515
types: [opened, reopened, closed, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled]
16+
schedule:
17+
# run cleaning of warning comments twice a day
18+
- cron: '00 6,18 * * *'
1619

1720
jobs:
1821
synchronize:
22+
if: vars.SYNC_LABELS_ACTIVE == 'yes' # variable from repository settings to suspend the job
1923
runs-on: ubuntu-latest
2024
steps:
2125
# Checkout the Python script
@@ -25,10 +29,11 @@ jobs:
2529
files: .github/sync_labels.py
2630

2731
# Perform synchronization
28-
- name: Call script
32+
- name: Call script for synchronization
33+
if: github.event.schedule == ''
2934
run: |
3035
chmod a+x .github/sync_labels.py
31-
.github/sync_labels.py $ACTION $ISSUE_URL $PR_URL $ACTOR "$LABEL" "$REV_STATE"
36+
.github/sync_labels.py $ACTION $ISSUE_URL $PR_URL $ACTOR "$LABEL" "$REV_STATE" $LOG_LEVEL
3237
env:
3338
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3439
ACTION: ${{ github.event.action }}
@@ -37,3 +42,15 @@ jobs:
3742
ACTOR: ${{ github.actor }}
3843
LABEL: ${{ github.event.label.name }}
3944
REV_STATE: ${{ github.event.review.state }}
45+
LOG_LEVEL: ${{ vars.SYNC_LABELS_LOG_LEVEL }} # variable from repository settings, values can be "--debug", "--info" or "--warning"
46+
47+
# Perform cleaning
48+
- name: Call script for cleaning
49+
if: github.event.schedule != ''
50+
run: |
51+
chmod a+x .github/sync_labels.py
52+
.github/sync_labels.py $REPO_URL $LOG_LEVEL
53+
env:
54+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55+
REPO_URL: ${{ github.event.repository.html_url }}
56+
LOG_LEVEL: ${{ vars.SYNC_LABELS_LOG_LEVEL }} # variable from repository settings, values can be "--debug", "--info" or "--warning"

0 commit comments

Comments
 (0)