From 79183e24b413b1687b072867d4d494bb9211e96f Mon Sep 17 00:00:00 2001 From: "Matthew R. Becker" Date: Wed, 27 Nov 2024 17:04:46 -0600 Subject: [PATCH] fix: try two public urls for maintainer exists lint (#2171) * fix: try two public urls for maintainer exists lint * test: add test for org * fix: need to use the teams endpoint * fix: ignore redirects and use more specific urls * doc: add news item --- conda_smithy/lint_recipe.py | 42 ++++++++++++++++++++++++----- news/2171-fix-maintainer_exists.rst | 24 +++++++++++++++++ tests/test_lint_recipe.py | 36 +++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 news/2171-fix-maintainer_exists.rst diff --git a/conda_smithy/lint_recipe.py b/conda_smithy/lint_recipe.py index 67dbdb909..8938fdb1c 100644 --- a/conda_smithy/lint_recipe.py +++ b/conda_smithy/lint_recipe.py @@ -413,16 +413,44 @@ def _maintainer_exists(maintainer: str) -> bool: gh = _cached_gh() try: gh.get_user(maintainer) + is_user = True except github.UnknownObjectException: - return False - return True + is_user = False + + # for w/e reason, the user endpoint returns an entry for orgs + # however the org endpoint does not return an entry for users + # so we have to check both + try: + gh.get_organization(maintainer) + is_org = True + except github.UnknownObjectException: + is_org = False else: - return ( - requests.get( - f"https://api.github.com/users/{maintainer}" - ).status_code - == 200 + # this API request has no token and so has a restrictive rate limit + # return ( + # requests.get( + # f"https://api.github.com/users/{maintainer}" + # ).status_code + # == 200 + # ) + # so we check two public URLs instead. + # 1. github.com/?tab=repositories - this URL works for all users and all orgs + # 2. https://github.com/orgs//teams - this URL only works for + # orgs so we make sure it fails + # we do not allow redirects to ensure we get the correct status code + # for the specific URL we requested + req_profile = requests.get( + f"https://github.com/{maintainer}?tab=repositories", + allow_redirects=False, + ) + is_user = req_profile.status_code == 200 + req_org = requests.get( + f"https://github.com/orgs/{maintainer}/teams", + allow_redirects=False, ) + is_org = req_org.status_code == 200 + + return is_user and not is_org @lru_cache(maxsize=1) diff --git a/news/2171-fix-maintainer_exists.rst b/news/2171-fix-maintainer_exists.rst new file mode 100644 index 000000000..f44239008 --- /dev/null +++ b/news/2171-fix-maintainer_exists.rst @@ -0,0 +1,24 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Fixed a bug in checking in recipe maintainers exist without a GitHub token + where the anonymous API would run out of requests. (#2171) + +**Security:** + +* diff --git a/tests/test_lint_recipe.py b/tests/test_lint_recipe.py index 4534fd7da..b49f19b31 100644 --- a/tests/test_lint_recipe.py +++ b/tests/test_lint_recipe.py @@ -1871,6 +1871,42 @@ def test_maintainer_exists(self): expected_message = 'Recipe maintainer "isuruf" does not exist' self.assertNotIn(expected_message, lints) + lints, _ = linter.lintify_meta_yaml( + {"extra": {"recipe-maintainers": ["conda-forge"]}}, + conda_forge=True, + ) + expected_message = 'Recipe maintainer "conda-forge" does not exist' + self.assertIn(expected_message, lints) + + def test_maintainer_exists_no_token(self): + gh_token = os.environ.get("GH_TOKEN", None) + try: + if "GH_TOKEN" in os.environ: + del os.environ["GH_TOKEN"] + + lints, _ = linter.lintify_meta_yaml( + {"extra": {"recipe-maintainers": ["support"]}}, + conda_forge=True, + ) + expected_message = 'Recipe maintainer "support" does not exist' + self.assertIn(expected_message, lints) + + lints, _ = linter.lintify_meta_yaml( + {"extra": {"recipe-maintainers": ["isuruf"]}}, conda_forge=True + ) + expected_message = 'Recipe maintainer "isuruf" does not exist' + self.assertNotIn(expected_message, lints) + + lints, _ = linter.lintify_meta_yaml( + {"extra": {"recipe-maintainers": ["conda-forge"]}}, + conda_forge=True, + ) + expected_message = 'Recipe maintainer "conda-forge" does not exist' + self.assertIn(expected_message, lints) + finally: + if gh_token is not None: + os.environ["GH_TOKEN"] = gh_token + def test_maintainer_team_exists(self): lints, _ = linter.lintify_meta_yaml( {