update_label_access.py 4.1 KB

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