Files
endoflife-date-release-data/src/github_releases.py
Marc Wrobel a0ba2d687e Improve scripts execution orchestration (#299)
Until now products could declare multiple auto-update methods, but they all had to be of the same kind.
For example if you used the git auto-update method, you could not use an additional github_releases or custom auto-update method.
This is an issue as it prevents us to extend the auto-update process, for example by having a product using the 'git' auto-update method to retrieve all the versions, and a custom script to retrieve support and EOL dates.

This improve the scripts execution orchestration to be able to support auto configurations using a mix of methods, meaning:

- multiple kind of methods, such as git and github_release,
- or multiple custom methods.

A side-effect of those changes is that now a failure in a generic script does not cancel the update of subsequent products.

Another side-effect, unwanted this time, is that now custom scripts managing multiple products, such as apple.py, are now executed multiple times instead of once.
2024-02-11 15:28:26 +01:00

60 lines
2.1 KiB
Python

import json
import logging
import subprocess
import sys
from common import dates, endoflife, releasedata
"""Fetches versions from GitHub releases using the GraphQL API and the GitHub CLI.
Note: GraphQL API and GitHub CLI are used because it's simpler: no need to manage pagination and authentication.
"""
METHOD = "github_releases"
def fetch_releases(repo_id: str) -> list[dict]:
logging.info(f"fetching {repo_id} GitHub releases")
(owner, repo) = repo_id.split('/')
child = subprocess.run("""gh api graphql --paginate -f query='
query($endCursor: String) {
repository(name: "%s", owner: "%s") {
releases(
orderBy: {field: NAME, direction: ASC}
first: 100
after: $endCursor
) {
pageInfo { hasNextPage, endCursor }
edges {
node {
name
publishedAt
isPrerelease
}
}
}
}
}'""" % (repo, owner), capture_output=True, timeout=300, check=True, shell=True) # noqa: UP031
logging.info(f"fetched {repo_id} GitHub releases")
# splitting because response may contain multiple JSON objects on a single line
responses = child.stdout.decode("utf-8").strip().replace('}{', '}\n{').split("\n")
return [json.loads(response) for response in responses]
p_filter = sys.argv[1] if len(sys.argv) > 1 else None
m_filter = sys.argv[2] if len(sys.argv) > 2 else None
for config in endoflife.list_configs(p_filter, METHOD, m_filter):
with releasedata.ProductData(config.product) as product_data:
for page in fetch_releases(config.url):
releases = [edge['node'] for edge in (page['data']['repository']['releases']['edges'])]
for release in releases:
if not release['isPrerelease']:
version_str = release['name']
version_match = config.first_match(version_str)
if version_match:
version = config.render(version_match)
date = dates.parse_datetime(release['publishedAt'])
product_data.declare_version(version, date)