Skip to content

format approve script #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 152 additions & 107 deletions github-approve-label-workflow/github-approve-label-workflow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! /usr/bin/env python
#! /usr/bin/env python3
# requires python 3
#
# Do we have any open PR's that have label "Approval: done"
Expand All @@ -11,161 +11,206 @@
# as of Feb 2020
#
# [email protected] Feb 2020
#
import requests
import json
from datetime import datetime, timezone
from optparse import OptionParser
import os
from datetime import datetime
from datetime import timezone

import requests

DEBUG = os.getenv("DEBUG", "").lower() == "true"
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
COMMIT = os.getenv("COMMIT", "").lower() == "true"

API_URL = "https://api.github.com/repos/openssl/openssl"

HEADERS = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {GITHUB_TOKEN}",
"X-GitHub-Api-Version": "2022-11-28",
}

api_url = "https://api.github.com/repos/openssl/openssl"

def convertdate(date):
return datetime.strptime(date.replace('Z',"+0000"), "%Y-%m-%dT%H:%M:%S%z")
return datetime.strptime(date.replace("Z", "+0000"), "%Y-%m-%dT%H:%M:%S%z")

# Get all the open pull requests, filtering by approval: done label

def getpullrequests():
url = api_url + "/pulls?per_page=100&page=1" # defaults to open
res = requests.get(url, headers=headers)
"""
Get all the open pull requests, filtering by approval: done label
"""
url = f"{API_URL}/pulls?per_page=100&page=1" # defaults to open
res = requests.get(url, headers=HEADERS)
repos = res.json()
prs = []
while 'next' in res.links.keys():
res = requests.get(res.links['next']['url'], headers=headers)
while "next" in res.links.keys():
res = requests.get(res.links["next"]["url"], headers=HEADERS)
repos.extend(res.json())

# Let's filter by label if we're just looking to move things, we can parse
# everything for statistics in another script

try:
for pr in repos:
if 'labels' in pr:
for label in pr['labels']:
if label['name'] == 'approval: done':
prs.append(pr['number'])
except:
print("failed", repos['message'])
if "labels" in pr:
for label in pr["labels"]:
if label["name"] == "approval: done":
prs.append(pr["number"])
except Exception:
print("failed", repos["message"])
return prs

# Change the labels on an issue from approval: done to approval: ready to merge

def movelabeldonetoready(issue):
url = api_url + "/issues/" + str(issue) + "/labels/approval:%20done"
res = requests.delete(url, headers=headers)
if (res.status_code != 200):
"""
Change the labels on an issue from approval: done to approval: ready to merge
"""
url = f"{API_URL}/issues/{issue}/labels/approval:%20done"
res = requests.delete(url, headers=HEADERS)
if not res.ok:
print("Error removing label", res.status_code, res.content)
return
url = api_url + "/issues/" + str(issue) + "/labels"
url = f"{API_URL}/issues/{issue}/labels"
newlabel = {"labels": ["approval: ready to merge"]}
res = requests.post(url, data=json.dumps(newlabel), headers=headers)
if (res.status_code != 200):
res = requests.post(url, data=json.dumps(newlabel), headers=HEADERS)
if not res.ok:
print("Error adding label", res.status_code, res.content)
return
newcomment = {"body":"This pull request is ready to merge"}
url = api_url + "/issues/" + str(issue) + "/comments"
res = requests.post(url, data=json.dumps(newcomment), headers=headers)
if (res.status_code != 201):
newcomment = {"body": "This pull request is ready to merge"}
url = f"{API_URL}/issues/{issue}/comments"
res = requests.post(url, data=json.dumps(newcomment), headers=HEADERS)
if res.status_code != 201:
print("Error adding comment", res.status_code, res.content)
return

# Check through an issue and see if it's a candidate for moving

def addcomment(issue, text):
"""
Add a comment to PR
"""
newcomment = {"body": text}
url = f"{API_URL}/issues/{issue}/comments"
res = requests.post(url, data=json.dumps(newcomment), headers=HEADERS)
if res.status_code != 201:
print("Error adding comment", res.status_code, res.content)


def checkpr(pr):
url = api_url + "/issues/" + str(pr) + "/timeline?per_page=100&page=1"
res = requests.get(url, headers=headers)
"""
Check through an issue and see if it's a candidate for moving
"""
url = f"{API_URL}/issues/{pr}/timeline?per_page=100&page=1"
res = requests.get(url, headers=HEADERS)
repos = res.json()
while 'next' in res.links.keys():
res = requests.get(res.links['next']['url'], headers=headers)
while "next" in res.links.keys():
res = requests.get(res.links["next"]["url"], headers=HEADERS)
repos.extend(res.json())

comments = []
opensslmachinecomments = []
approvallabel = {}
readytomerge = 0
sha = ""

for event in repos:
try:
if (event['event'] == "commented"):
comments.append(convertdate(event["updated_at"]))
if debug:
print("debug: commented at ",
convertdate(event["updated_at"]))
if (event['event'] == "committed"):
if event["event"] == "commented":
if "openssl-machine" in event["actor"]["login"]:
opensslmachinecomments.append(convertdate(event["updated_at"]))
else:
comments.append(convertdate(event["updated_at"]))
if DEBUG:
print("DEBUG: commented at ", convertdate(event["updated_at"]))
if event["event"] == "committed":
sha = event["sha"]
comments.append(convertdate(event["author"]["date"]))
if debug:
print("debug: created at ",
convertdate(event["author"]["date"]))
elif (event['event'] == "labeled"):
if debug:
print("debug: labelled with ", event['label']['name'],
"at", convertdate(event["created_at"]))
approvallabel[event['label']['name']] = convertdate(
event["created_at"])
elif (event['event'] == "unlabeled"):
if (debug):
print("debug: unlabelled with ", event['label']['name'],
"at", convertdate(event["created_at"]))
if event['label'][
'name'] in approvallabel: # have to do this for if labels got renamed in the middle
del approvallabel[event['label']['name']]
elif (event['event'] == "reviewed"
and event['state'] == "approved"):
if debug:
print("debug: approved at",
convertdate(event['submitted_at']))
except:
return (repos['message'])

if 'approval: ready to merge' in approvallabel:
return ("issue already has label approval: ready to merge")
if 'approval: done' not in approvallabel:
return ("issue did not get label approval: done")
approvedone = approvallabel['approval: done']

if max(comments) > approvedone:
return ("issue had comments after approval: done label was given")
if DEBUG:
print("DEBUG: created at ", convertdate(event["author"]["date"]))
elif event["event"] == "labeled":
if DEBUG:
print("DEBUG: labelled with ", event["label"]["name"], "at", convertdate(event["created_at"]))
approvallabel[event["label"]["name"]] = convertdate(event["created_at"])
elif event["event"] == "unlabeled":
if DEBUG:
print("DEBUG: unlabelled with ", event["label"]["name"], "at", convertdate(event["created_at"]))
# have to do this for if labels got renamed in the middle
if event["label"]["name"] in approvallabel:
del approvallabel[event["label"]["name"]]
elif event["event"] == "reviewed" and event["state"] == "approved":
if DEBUG:
print("DEBUG: approved at", convertdate(event["submitted_at"]))
except Exception:
return repos["message"]

if "approval: ready to merge" in approvallabel:
return "issue already has label approval: ready to merge"
if "approval: done" not in approvallabel:
return "issue did not get label approval: done"
if "urgent" in approvallabel:
labelurgent = approvallabel["urgent"]
if labelurgent and max(comments) <= labelurgent:
print("issue is urgent and has had no comments so needs a comment")
if COMMIT:
addcomment(pr, "@openssl/committers note this pull request has had the urgent label applied")

approvedone = approvallabel["approval: done"]

now = datetime.now(timezone.utc)
hourssinceapproval = (now - approvedone).total_seconds() / 3600
if debug:
if DEBUG:
print("Now: ", now)
print("Last comment: ", max(comments))
print("Approved since: ", approvedone)
print("hours since approval", hourssinceapproval)

if (hourssinceapproval < 24):
return ("not yet 24 hours since labelled approval:done hours:" +
str(int(hourssinceapproval)))
if hourssinceapproval < 24:
return f"not yet 24 hours since labelled approval:done hours: {hourssinceapproval}"

if (options.commit):
if max(comments) > approvedone:
if len(opensslmachinecomments) and (max(opensslmachinecomments) > approvedone):
return "issue had comments after approval but we have already added a comment about this"
if COMMIT:
comment = (
"24 hours has passed since 'approval: done' was set, but as this PR has been "
" updated in that time the label 'approval: ready to merge' is not being "
"automatically set. Please review the updates and set the label manually."
)
addcomment(pr, comment)
return "issue had comments after approval: done label was given, made a comment"

# Final check before changing the label, did CI pass?

url = f"{API_URL}/commits/{sha}/status"
res = requests.get(url, headers=HEADERS)
if not res.ok:
return "PR has unknown CI status"
ci = res.json()
if ci["state"] != "success":
if len(opensslmachinecomments) and (max(opensslmachinecomments) > approvedone):
return "issue has CI failure but we have already added a comment about this"
if COMMIT:
comment = (
"24 hours has passed since 'approval: done' was set, but this PR has failing CI tests. "
"Once the tests pass it will get moved to 'approval: ready to merge' automatically,"
"alternatively please review and set the label manually."
)
addcomment(pr, comment)
return "PR has CI failure, made a comment"
if COMMIT:
print("Moving issue ", pr, " to approval: ready to merge")
movelabeldonetoready(pr)
else:
print("use --commit to actually change the labels")
return (
"this issue was candidate to move to approval: ready to merge hours:" +
str(int(hourssinceapproval)))

# main

parser = OptionParser()
parser.add_option("-d","--debug",action="store_true",help="be noisy",dest="debug")
parser.add_option("-t","--token",help="file containing github authentication token for example 'token 18asdjada...'",dest="token")
parser.add_option("-c","--commit",action="store_true",help="actually change the labels",dest="commit")
(options, args) = parser.parse_args()
if (options.token):
fp = open(options.token, "r")
git_token = fp.readline().strip('\n')
else:
git_token = "" # blank token is fine, but you can't change labels and you hit API rate limiting
debug = options.debug
# since timeline is a preview feature we have to enable access to it with an accept header
headers = {
"Accept": "application/vnd.github.mockingbird-preview",
"Authorization": git_token
}
print("set COMMIT env var to actually change the labels")
return f"this issue was candidate to move to approval: ready to merge hours: {hourssinceapproval}"


def main():
if DEBUG:
print("Getting list of PRs")
prs = getpullrequests()
print(f"There were {len(prs)} open PRs with approval:done")
for pr in prs:
print(pr, checkpr(pr))


if debug:
print("Getting list of PRs")
prs = getpullrequests()
print("There were", len(prs), "open PRs with approval:done ")
for pr in prs:
print(pr, checkpr(pr))
if __name__ == "__main__":
main()