update_label_access.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. """Updates the contributors-with-label-access team.
  2. This team exists because we need a team to manage triage access to repos;
  3. GitHub doesn't allow the org to be set to triage access, only read/write. It
  4. will be updated to include all members of the carbon-language organization.
  5. """
  6. __copyright__ = """
  7. Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  8. Exceptions. See /LICENSE for license information.
  9. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  10. """
  11. import argparse
  12. from typing import List, Optional, Set
  13. # https://github.com/PyGithub/PyGithub
  14. # GraphQL is preferred, but falling back to pygithub for unsupported mutations.
  15. import github
  16. from github_tools import github_helpers
  17. # The organization to mirror members from.
  18. _ORG = "carbon-language"
  19. # The team to mirror to.
  20. _TEAM = "contributors"
  21. # Accounts in the org to skip mirroring.
  22. _IGNORE_ACCOUNTS = ("CarbonLangInfra", "google-admin", "googlebot")
  23. # Queries organization members.
  24. _ORG_MEMBER_QUERY = """
  25. query {
  26. organization(login: "%s") {
  27. membersWithRole(first: 100%%(cursor)s) {
  28. nodes {
  29. login
  30. }
  31. %%(pagination)s
  32. }
  33. }
  34. }
  35. """
  36. # The path for nodes in _ORG_MEMBER_QUERY.
  37. _ORG_MEMBER_PATH = ("organization", "membersWithRole")
  38. # Queries team members.
  39. _TEAM_MEMBER_QUERY = """
  40. query {
  41. organization(login: "%s") {
  42. team(slug: "%s") {
  43. members(first: 100%%(cursor)s) {
  44. nodes {
  45. login
  46. }
  47. %%(pagination)s
  48. }
  49. }
  50. }
  51. }
  52. """
  53. # The path for nodes in _TEAM_MEMBER_QUERY.
  54. _TEAM_MEMBER_PATH = ("organization", "team", "members")
  55. def _parse_args(args: Optional[List[str]] = None) -> argparse.Namespace:
  56. """Parses command-line arguments and flags."""
  57. parser = argparse.ArgumentParser(description=__doc__)
  58. github_helpers.add_access_token_arg(parser, "admin:org, repo")
  59. return parser.parse_args(args=args)
  60. def _load_org_members(client: github_helpers.Client) -> Set[str]:
  61. """Loads org members."""
  62. print("Loading %s..." % _ORG)
  63. org_members = set()
  64. ignored = set()
  65. for node in client.execute_and_paginate(
  66. _ORG_MEMBER_QUERY % _ORG, _ORG_MEMBER_PATH
  67. ):
  68. login = node["login"]
  69. if login not in _IGNORE_ACCOUNTS:
  70. org_members.add(login)
  71. else:
  72. ignored.add(login)
  73. print(
  74. "%s has %d non-ignored members, and %d ignored."
  75. % (_ORG, len(org_members), len(ignored))
  76. )
  77. unignored = set(_IGNORE_ACCOUNTS) - ignored
  78. assert not unignored, "Missing ignored accounts: %s" % unignored
  79. return org_members
  80. def _load_team_members(client: github_helpers.Client) -> Set[str]:
  81. """Load team members."""
  82. print("Loading %s..." % _TEAM)
  83. team_members = set()
  84. for node in client.execute_and_paginate(
  85. _TEAM_MEMBER_QUERY % (_ORG, _TEAM), _TEAM_MEMBER_PATH
  86. ):
  87. team_members.add(node["login"])
  88. print("%s has %d members." % (_ORG, len(team_members)))
  89. return team_members
  90. def _update_team(
  91. gh: github.Github, org_members: Set[str], team_members: Set[str]
  92. ) -> None:
  93. """Updates the team if needed.
  94. This switches to pygithub because GraphQL lacks equivalent mutation support.
  95. """
  96. gh_team = gh.get_organization(_ORG).get_team_by_slug(_TEAM)
  97. add_members = org_members - team_members
  98. if add_members:
  99. print("Adding members: %s" % ", ".join(add_members))
  100. for member in add_members:
  101. gh_team.add_membership(gh.get_user(member))
  102. remove_members = team_members - org_members
  103. if remove_members:
  104. print("Removing members: %s" % ", ".join(remove_members))
  105. for member in remove_members:
  106. gh_team.remove_membership(gh.get_user(member))
  107. def main() -> None:
  108. parsed_args = _parse_args()
  109. print("Connecting...")
  110. client = github_helpers.Client(parsed_args)
  111. org_members = _load_org_members(client)
  112. team_members = _load_team_members(client)
  113. if org_members != team_members:
  114. gh = github.Github(parsed_args.access_token)
  115. _update_team(gh, org_members, team_members)
  116. print("Done!")
  117. if __name__ == "__main__":
  118. main()