timescaledb/scripts/check_changelog_format.py
Jan Nidzwetzki 4cb7bacd17 Change GitHub token for changelog check
Since #6505, the changelog script tries to access the
secrets.ORG_AUTOMATION_TOKEN. However, accessing secrets is not possible
for PRs. This PR changes the token to the default access token, which
is available in PRs and provides read access to the issue API.
2024-01-23 11:15:52 +01:00

147 lines
4.4 KiB
Python
Executable File

#!/usr/bin/env python3
import sys
import re
import os
import github # this is PyGithub.
import requests
import string
def run_query(query):
"""A simple function to use requests.post to make the GraphQL API call."""
request = requests.post(
"https://api.github.com/graphql",
json={"query": query},
headers={"Authorization": f'Bearer {os.environ.get("GITHUB_TOKEN")}'},
timeout=20,
)
response = request.json()
# Have to work around the unique GraphQL convention of returning 200 for errors.
if request.status_code != 200 or "errors" in response:
raise ValueError(
f"Query failed to run by returning code of {request.status_code}."
f"\nQuery: '{query}'"
f"\nResponse: '{request.json()}'"
)
return response
def get_referenced_issues(pr_number):
"""Get the numbers of issue fixed by the given pull request."""
ref_result = run_query(
string.Template(
"""
query {
repository(owner: "timescale", name: "timescaledb") {
pullRequest(number: $pr_number) {
closingIssuesReferences(first: 100) {
edges {
node {
number
}
}
}
}
}
}
"""
).substitute({"pr_number": pr_number})
)
# The above returns {'data': {'repository': {'pullRequest': {'closingIssuesReferences': {'edges': [{'node': {'number': 4944}}]}}}}}
ref_edges = ref_result["data"]["repository"]["pullRequest"][
"closingIssuesReferences"
]["edges"]
if not ref_edges:
return []
return [edge["node"]["number"] for edge in ref_edges if edge]
# Check if a line matches any of the specified patterns
def is_valid_line(line):
patterns = [r"^Fixes:\s*.*$", r"^Implements:\s*.*$", r"^Thanks:\s*.*$"]
for pattern in patterns:
if re.match(pattern, line):
return True
return False
def main():
github_token = os.environ.get("GITHUB_TOKEN")
if not github_token:
print("Please populate the GITHUB_TOKEN environment variable.")
sys.exit(1)
github_obj = github.Github(github_token)
repo = github_obj.get_repo("timescale/timescaledb")
# Get the file name from the command line argument
if len(sys.argv) != 2:
print("Please provide a file name as a command-line argument.")
sys.exit(1)
file_name = sys.argv[1]
this_pr_number = int(os.environ["PR_NUMBER"])
pr_issues = set(get_referenced_issues(this_pr_number))
# Read the file and check non-empty lines
changelog_issues = set()
with open(file_name, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if not is_valid_line(line):
print(f'Invalid entry in change log: "{line}"')
sys.exit(1)
# The referenced issue number should be valid.
for issue_number in re.findall("#([0-9]+)", line):
issue_number = int(issue_number)
try:
issue = repo.get_issue(number=issue_number)
except github.UnknownObjectException:
print(
f"The changelog entry references an invalid issue #{issue_number}:\n{line}"
)
sys.exit(1)
as_pr = None
try:
as_pr = issue.as_pull_request()
except github.UnknownObjectException:
# Not a pull request
pass
# Accept references to PR itself.
if as_pr:
if issue_number != this_pr_number:
print(
f"The changelog for PR #{this_pr_number} references another PR #{issue_number}"
)
sys.exit(1)
changelog_issues = pr_issues
else:
changelog_issues.add(issue_number)
if changelog_issues != pr_issues:
print(
"Instead of "
+ (f"the issues {pr_issues}" if pr_issues else "no issues")
+ f" linked to the PR #{this_pr_number}, the changelog references "
+ (f"the issues {changelog_issues}" if changelog_issues else "no issues")
)
sys.exit(1)
if __name__ == "__main__":
main()