diff --git a/.github/actions/draft-release/update_draft_release.py b/.github/actions/draft-release/update_draft_release.py index e753c1465..c69cd6b57 100755 --- a/.github/actions/draft-release/update_draft_release.py +++ b/.github/actions/draft-release/update_draft_release.py @@ -16,6 +16,7 @@ print(f'note: added {repo_root} to python-path (sys.path)') import github.util +import github.release import version as version_mod @@ -76,16 +77,24 @@ def main(): name=repo, github_api=github_api, ) + repository = github_helper.repository with open(parsed.release_notes) as f: release_notes_md = f.read() draft_release_name = f'{version}{parsed.draftname_suffix}' - if not (draft_release := github_helper.draft_release_with_name(draft_release_name)): + release_notes_md = github.release.body_or_replacement( + release_notes_md, + ) + if not (draft_release := github.release.find_draft_release( + repository=repository, + name=draft_release_name, + )): print(f'Creating {draft_release_name=}') - github_helper.create_draft_release( - name=draft_release_name, + repository.create_release( + tag_name=draft_release_name, body=release_notes_md, + draft=True, ) else: if not draft_release.body == release_notes_md: diff --git a/concourse/steps/draft_release.mako b/concourse/steps/draft_release.mako index 149339e0e..85e4abb18 100644 --- a/concourse/steps/draft_release.mako +++ b/concourse/steps/draft_release.mako @@ -28,6 +28,7 @@ import ci.log import ci.util import cnudie.retrieve import cnudie.util +import github.release import github.util import gitutil import ocm @@ -122,18 +123,27 @@ except ValueError as e: # repository is already published - usually by steps that erroneously publish them before they should. release_notes_md = 'no release notes available' +repository = github_helper.repository draft_name = f'{processed_version}-draft' -draft_release = github_helper.draft_release_with_name(draft_name) +draft_release = github.release.find_draft_release( + repository=repository, + name=draft_name, +) +body = github.body_or_replacement( + body=release_notes_md, +) if not draft_release: - logger.info(f"Creating draft-release '{draft_name}'") - github_helper.create_draft_release( - name=draft_name, - body=release_notes_md, + logger.info(f"Creating {draft_name=}") + repository.create_release( + tag_name=draft_name, + body=body, + draft=True, + prerelease=False, ) else: - if not draft_release.body == release_notes_md: + if not draft_release.body == body: logger.info(f"Updating draft-release '{draft_name}'") - draft_release.edit(body=release_notes_md) + draft_release.edit(body=body) else: logger.info('draft release notes are already up to date') diff --git a/concourse/steps/release.py b/concourse/steps/release.py index eb3f9fc5c..2bf547890 100644 --- a/concourse/steps/release.py +++ b/concourse/steps/release.py @@ -22,6 +22,7 @@ import concourse.steps.version import concourse.model.traits.version as version_trait import dockerutil +import github.release import release_notes.fetch import release_notes.markdown import slackclient.util @@ -400,7 +401,10 @@ def github_release( # github-api expects unqualified tagname release_tag = release_tag.removeprefix('refs/tags/') - if release := github_helper.draft_release_with_name(f'{release_version}-draft'): + if release := github.release.find_draft_release( + repository=github_helper.repository, + name=f'{release_version}-draft', + ): github_helper.promote_draft_release( draft_release=release, release_tag=release_tag, diff --git a/github/limits.py b/github/limits.py new file mode 100644 index 000000000..88344c48a --- /dev/null +++ b/github/limits.py @@ -0,0 +1,20 @@ +''' +limits for github-api + +stolen from: https://github.com/dead-claudia/github-limits + +limits refer to amount of codepoints (tested empirically for some samples). +''' + +issue_body = 65536 +issue_title = 256 +pullrequest_body = 262144 +release_body = 125000 + + +def fits( + value: str | bytes, + /, + limit: int, +) -> bool: + return len(value) <= limit diff --git a/github/release.py b/github/release.py new file mode 100644 index 000000000..81f9dcba5 --- /dev/null +++ b/github/release.py @@ -0,0 +1,55 @@ +''' +utils wrapping github3.py's relase-API +''' + +import github3.repos +import github3.repos.release + +import github.limits + + +def body_or_replacement( + body: str, + replacement: str='body was too large (limit: {limit} / actual: {actual})', + limit: int=github.limits.release_body, +) -> tuple[str, bool]: + ''' + convenience function that will check whether given body is short enough to be accepted + by GitHub's API. If so, passed body will be returned as first element of returned tuple, else + replacement value. + + The second value of returned tuple will indicate whether original body was returned. Callers + may use this hint to perform a mitigation. + + limit may be overwritten (but this is not recommended; see github.limits for more details). + ''' + if github.limits.fits( + body, + limit=limit, + ): + return body, True + + return replacement.format( + limit=limit, + actual=len(body), + ), False + + +def find_draft_release( + repository: github3.repos.Repository, + name: str, +) -> github3.repos.release.Release | None: + ''' + finds the given draft-release. For draft-releases, lookup has to be done that way, as + there is no way of directly retrieving a draft-release (as those do not yet have a tag) + ''' + # at some point in time, github.com would return http-500 if there were more than 1020 + # releases; as draft-releases are typically not too old (and such great numbers of releases + # are uncommon), this should be okay to hardcode. Todo: check whether this limit is still + # valid. + max_releases = 1020 + for release in repository.releases(number=max_releases): + if not release.draft: + continue + if release.name == name: + return release diff --git a/github/util.py b/github/util.py index 3b679c5a5..1a55dbe1e 100644 --- a/github/util.py +++ b/github/util.py @@ -4,7 +4,6 @@ import collections -import datetime import enum import logging import re @@ -21,8 +20,8 @@ from github3.pulls import PullRequest from github3.repos.release import Release -import ocm import ci.util +import ocm import version logger = logging.getLogger(__name__) @@ -32,6 +31,7 @@ This limit is not documented explicitly in the GitHub docs. To see it, the error returned by GitHub when creating a release with more then the allowed number of characters must be looked at. +as (inofficial) alternative, see: https://github.com/dead-claudia/github-limits ''' MAXIMUM_GITHUB_RELEASE_BODY_LENGTH = 25000 @@ -390,18 +390,6 @@ def create_release( ) return release - def create_draft_release( - self, - name: str, - body: str, - ): - return self.create_release( - tag_name='', - name=name, - body=body, - draft=True, - ) - def promote_draft_release( self, draft_release, @@ -468,21 +456,6 @@ def update_release_notes( return release - def draft_release_with_name( - self, - name: str - ) -> Release: - # if there are more than 1021 releases, github(.com) will return http-500 one requesting - # additional releases. As this limit is typically not reached, hardcode limit for now - # in _most_ cases, most recent releases are returned first, so this should hardly ever - # be an actual issue - max_releases = 1020 - for release in self.repository.releases(number=max_releases): - if not release.draft: - continue - if release.name == name: - return release - def tag_exists( self, tag_name: str,