update_label_access.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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. # https://github.com/PyGithub/PyGithub
  14. # GraphQL is preferred, but falling back to pygithub for unsupported mutations.
  15. import github
  16. import github_helpers
  17. # The organization to mirror members from.
  18. _ORG = "carbon-language"
  19. # The team to mirror to.
  20. _TEAM = "contributors-with-label-access"
  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=None):
  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):
  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):
  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(gh, org_members, team_members):
  91. """Updates the team if needed.
  92. This switches to pygithub because GraphQL lacks equivalent mutation support.
  93. """
  94. gh_team = gh.get_organization(_ORG).get_team_by_slug(_TEAM)
  95. add_members = org_members - team_members
  96. if add_members:
  97. print("Adding members: %s" % ", ".join(add_members))
  98. for member in add_members:
  99. gh_team.add_membership(gh.get_user(member))
  100. remove_members = team_members - org_members
  101. if remove_members:
  102. print("Removing members: %s" % ", ".join(remove_members))
  103. for member in remove_members:
  104. gh_team.remove_membership(gh.get_user(member))
  105. def main():
  106. parsed_args = _parse_args()
  107. print("Connecting...")
  108. client = github_helpers.Client(parsed_args)
  109. org_members = _load_org_members(client)
  110. team_members = _load_team_members(client)
  111. if org_members != team_members:
  112. gh = github.Github(parsed_args.access_token)
  113. _update_team(gh, org_members, team_members)
  114. print("Done!")
  115. if __name__ == "__main__":
  116. main()