Skip to content

Commit

Permalink
cleanup: mv more code -> github.release + drop "heritage"
Browse files Browse the repository at this point in the history
Reduce dependencies against GitHubRepositoryHelper. Mv release-related
code as functions -> github.release and adjust using code. Drop some
unused (I strongly think) code from CLI-collection, thus freeing even
more code.

As a result, draft-release-action no longer needs github.util / GHRH at
all.
  • Loading branch information
ccwienk committed Jan 28, 2025
1 parent 4289774 commit 103bbd1
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 239 deletions.
14 changes: 4 additions & 10 deletions .github/actions/draft-release/update_draft_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
import github3

try:
import github.util
import github.release
except ImportError:
# make local development more comfortable
repo_root = os.path.join(os.path.dirname(__file__), '../../..')
sys.path.insert(1, repo_root)
print(f'note: added {repo_root} to python-path (sys.path)')
import github.util
import github.release

import github.release
import version as version_mod


Expand Down Expand Up @@ -72,12 +71,7 @@ def main():
token=parsed.github_auth_token,
)

github_helper = github.util.GitHubRepositoryHelper(
owner=org,
name=repo,
github_api=github_api,
)
repository = github_helper.repository
repository = github_api.repository(org, repo)

with open(parsed.release_notes) as f:
release_notes_md = f.read()
Expand All @@ -101,7 +95,7 @@ def main():
print(f'Updating {draft_release_name=}')
draft_release.edit(body=release_notes_md)

for release, deleted in github_helper.delete_outdated_draft_releases():
for release, deleted in github.release.delete_outdated_draft_releases(repository):
if deleted:
print('Deleted obsolete draft {release.name=}')
else:
Expand Down
74 changes: 12 additions & 62 deletions cli/gardener_ci/githubutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,12 @@
#
# SPDX-License-Identifier: Apache-2.0

import urllib

from ci.util import (
ctx,
)
from github.util import (
GitHubRepositoryHelper,
find_greatest_github_release_version,
outdated_draft_releases,

)
import github.release
import ccc.github

import github3


def list_draft_releases(
github_cfg_name: str,
Expand All @@ -38,72 +29,31 @@ def list_draft_releases(
not equal to 0.
'''
github_cfg = ctx().cfg_factory().github(github_cfg_name)
github_helper = GitHubRepositoryHelper(
github_api = ccc.github.github_api(github_cfg)

repository = github_api.repository(
owner=github_repository_owner,
name=github_repository_name,
github_api=ccc.github.github_api(github_cfg),
repository=github_repository_name,
)

if only_outdated:
releases = [release for release in github_helper.repository.releases()]
releases = [release for release in repository.releases()]
non_draft_releases = [release for release in releases if not release.draft]
greatest_release_version = find_greatest_github_release_version(non_draft_releases)
greatest_release_version = github.release.find_greatest_github_release_version(
non_draft_releases,
)
else:
releases = github_helper.repository.releases()
releases = repository.releases()

draft_releases = [release for release in releases if release.draft]

if only_outdated:
if greatest_release_version is not None:
draft_releases = outdated_draft_releases(
draft_releases = github.release.outdated_draft_releases(
draft_releases=draft_releases,
greatest_release_version=greatest_release_version,
)
else:
draft_releases = []
for draft_release in draft_releases:
print(draft_release.name)


def greatest_release_version(
github_repository_url: str,
anonymous: bool=False,
ignore_prereleases: bool=False,
):
'''Find the release with the greatest name (according to semver) and print its semver-version.
Note:
- This will only consider releases whose names are either immediately parseable as semver-
versions, or prefixed with a single character ('v').
- The 'v'-prefix (if present) will be not be present in the output.
- If a release has no name, its tag will be used instead of its name.
For more details on the ordering of semantic versioning, see 'https://www.semver.org'.
'''
parse_result = urllib.parse.urlparse(github_repository_url)

if not parse_result.netloc:
raise ValueError(f'Could not determine host for github-url {github_repository_url}')
host = parse_result.netloc

try:
path = parse_result.path.strip('/')
org, repo = path.split('/')
except ValueError as e:
raise ValueError(f"Could not extract org- and repo-name. Error: {e}")

if anonymous:
if 'github.com' not in host:
raise ValueError("Anonymous access is only possible for github.com")
github_api = github3.GitHub()
repo_helper = GitHubRepositoryHelper(owner=org, name=repo, github_api=github_api)

else:
repo_helper = ccc.github.repo_helper(host=host, org=org, repo=repo)

print(
find_greatest_github_release_version(
releases=repo_helper.repository.releases(),
warn_for_unparseable_releases=False,
ignore_prerelease_versions=ignore_prereleases,
)
)
2 changes: 1 addition & 1 deletion concourse/steps/draft_release.mako
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ else:
logger.info('draft release notes are already up to date')
logger.info("Checking for outdated draft releases to delete")
for release, deletion_successful in github_helper.delete_outdated_draft_releases():
for release, deletion_successful in github.release.delete_outdated_draft_releases(repository):
if deletion_successful:
logger.info(f"Deleted release '{release.name}'")
else:
Expand Down
10 changes: 6 additions & 4 deletions concourse/steps/release.mako
Original file line number Diff line number Diff line change
Expand Up @@ -463,10 +463,13 @@ except:
pass
% if release_trait.release_on_github():
repo = github_helper.repository
try:
clean_draft_releases(
github_helper=github_helper,
)
for releases, succeeded in github.release.delete_outdated_draft_releases(repo):
if succeeded:
logger.info(f'deleted {release.name=}')
else:
logger.warn(f'failed to delete {release.name=}')
except:
logger.warning('An Error occurred whilst trying to remove draft-releases')
traceback.print_exc()
Expand All @@ -491,7 +494,6 @@ except:
release_notes_md = None
% endif
repo = github_helper.repository
release_tag = tags[0].removeprefix('refs/tags')
draft_tag = f'{version_str}-draft'
Expand Down
13 changes: 0 additions & 13 deletions concourse/steps/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@
import slackclient.util

from gitutil import GitHelper
from github.util import (
GitHubRepositoryHelper,
)
from concourse.model.traits.release import (
ReleaseCommitPublishingPolicy,
)
Expand Down Expand Up @@ -419,16 +416,6 @@ def upload_component_descriptor_as_release_asset(
logger.warning('Unable to attach component-descriptors to release as release-asset.')


def clean_draft_releases(
github_helper: GitHubRepositoryHelper,
):
for release, deletion_successful in github_helper.delete_outdated_draft_releases():
if deletion_successful:
logger.info(f'Deleted draft {release.name=}')
else:
logger.warning(f'Could not delete draft {release.name=}')


def post_to_slack(
release_notes_markdown,
component: ocm.Component,
Expand Down
137 changes: 137 additions & 0 deletions github/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
utils wrapping github3.py's relase-API
'''

import collections.abc as typehints
import logging

import github3.repos
import github3.repos.release

import github.limits
import version

logger = logging.getLogger(__name__)


def body_or_replacement(
Expand Down Expand Up @@ -53,3 +59,134 @@ def find_draft_release(
continue
if release.name == name:
return release


def delete_outdated_draft_releases(
repository: github3.repos.Repository,
) -> typehints.Generator[tuple[github3.repos.release.Release, bool], None, None]:
'''Find outdated draft releases and try to delete them
Yields tuples containing a release and a boolean indicating whether its deletion was
successful.
A draft release is considered outdated iff:
1: its version is smaller than the greatest release version (according to semver) AND
2a: it is NOT a hotfix draft release AND
2b: there are no hotfix draft releases with the same major and minor version
OR
3a: it is a hotfix draft release AND
3b: there is a hotfix draft release of greater version (according to semver)
with the same major and minor version
'''

releases = [release for release in repository.releases(number=20)]
non_draft_releases = [release for release in releases if not release.draft]
draft_releases = [release for release in releases if release.draft]
greatest_release_version = find_greatest_github_release_version(non_draft_releases)

if greatest_release_version is not None:
draft_releases_to_delete = outdated_draft_releases(
draft_releases=draft_releases,
greatest_release_version=greatest_release_version,
)
else:
draft_releases_to_delete = []

for release in draft_releases_to_delete:
yield release, release.delete()


def outdated_draft_releases(
draft_releases: list[github3.repos.release.Release],
greatest_release_version: str,
):
'''Find outdated draft releases from a list of draft releases and return them. This is achieved
by partitioning the release versions according to their joined major and minor version.
Partitions are then checked:
- if there is only a single release in a partition it is either a hotfix release
(keep corresponding release) or it is not (delete if it is not the greatest release
according to semver)
- if there are multiple releases versions in a partition, keep only the release
corresponding to greatest (according to semver)
'''

greatest_release_version_info = version.parse_to_semver(greatest_release_version)

def _has_semver_draft_prerelease_label(release_name):
version_info = version.parse_to_semver(release_name)
if version_info.prerelease != 'draft':
return False
return True

autogenerated_draft_releases = [
release for release in draft_releases
if release.name
and version.is_semver_parseable(release.name)
and _has_semver_draft_prerelease_label(release.name)
]

draft_release_version_infos = [
version.parse_to_semver(release.name)
for release in autogenerated_draft_releases
]

def _yield_outdated_version_infos_from_partition(partition):
if len(partition) == 1:
version_info = partition.pop()
if version_info < greatest_release_version_info and version_info.patch == 0:
yield version_info
else:
yield from [
version_info
for version_info in partition[1:]
]

outdated_version_infos = list()
for partition in version.partition_by_major_and_minor(draft_release_version_infos):
outdated_version_infos.extend(_yield_outdated_version_infos_from_partition(partition))

outdated_draft_releases = [
release
for release in autogenerated_draft_releases
if version.parse_to_semver(release.name) in outdated_version_infos
]

return outdated_draft_releases


def find_greatest_github_release_version(
releases: list[github3.repos.release.Release],
warn_for_unparseable_releases: bool = True,
ignore_prerelease_versions: bool = False,
):
# currently, non-draft-releases are not created with a name by us. Use the tag name as fallback
release_versions = [
release.name if release.name else release.tag_name
for release in releases
]

def filter_non_semver_parseable_releases(release_name):
try:
version.parse_to_semver(release_name)
return True
except ValueError:
if warn_for_unparseable_releases:
logger.warning(f'ignoring release {release_name=} (not semver)')
return False

release_versions = [
name for name in filter(filter_non_semver_parseable_releases, release_versions)
]

release_version_infos = [
version.parse_to_semver(release_version)
for release_version in release_versions
]
latest_version = version.find_latest_version(
versions=release_version_infos,
ignore_prerelease_versions=ignore_prerelease_versions,
)
if latest_version:
return str(latest_version)
else:
return None
Loading

0 comments on commit 103bbd1

Please sign in to comment.