hook_permissions.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2017, Open Source Robotics Foundation
  3. # All rights reserved.
  4. #
  5. # Redistribution and use in source and binary forms, with or without
  6. # modification, are permitted provided that the following conditions are met:
  7. #
  8. # * Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # * Redistributions in binary form must reproduce the above copyright
  11. # notice, this list of conditions and the following disclaimer in the
  12. # documentation and/or other materials provided with the distribution.
  13. # * Neither the name of the Willow Garage, Inc. nor the names of its
  14. # contributors may be used to endorse or promote products derived from
  15. # this software without specific prior written permission.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  18. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  21. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  22. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  23. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  24. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  25. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  26. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  27. # POSSIBILITY OF SUCH DAMAGE.
  28. from __future__ import print_function
  29. import argparse
  30. import os
  31. import sys
  32. from github import Github, UnknownObjectException
  33. def detect_repo_hook(repo, cb_url):
  34. for hook in repo.get_hooks():
  35. if hook.config.get('url') == cb_url:
  36. return True
  37. return False
  38. class GHPRBHookDetector(object):
  39. def __init__(self, github_user, github_token, callback_url):
  40. self.callback_url = callback_url
  41. self.gh = Github(github_user, github_token)
  42. def get_repo(self, username, reponame):
  43. try:
  44. repo = self.gh.get_user(username).get_repo(reponame)
  45. except UnknownObjectException as ex:
  46. print(
  47. 'Failed to access repo [ %s/%s ] Reason %s'
  48. % (username, reponame, ex),
  49. file=sys.stderr
  50. )
  51. return None
  52. return repo
  53. def check_repo_for_access(self, repo, errors, strict=False):
  54. push_access = repo.permissions.push
  55. admin_access = repo.permissions.admin
  56. try:
  57. hook_detected = detect_repo_hook(repo, self.callback_url)
  58. except UnknownObjectException as ex:
  59. errors.append('Unable to check repo [ %s ] for hooks: Error: %s' % (repo.full_name, ex))
  60. hook_detected = False
  61. if push_access and hook_detected or admin_access:
  62. return True
  63. if push_access and not hook_detected:
  64. print(
  65. 'Warning: Push access detected but unable to verify manual hook '
  66. 'configuration for repo [ %s ]. Please visit ' % repo.full_name +
  67. 'http://wiki.ros.org/buildfarm/Pull%20request%20testing '
  68. 'and make sure hooks are setup.',
  69. file=sys.stderr)
  70. if strict:
  71. return False
  72. else:
  73. errors.append(
  74. 'Warning: Push access detected but unable to verify manual hook '
  75. 'configuration for repo [ %s ]. Please visit ' % repo.full_name +
  76. 'http://wiki.ros.org/buildfarm/Pull%20request%20testing '
  77. 'and make sure hooks are setup.')
  78. return True
  79. def check_hooks_on_repo(user, repo, errors, hook_user='ros-pull-request-builder',
  80. callback_url='http://build.ros.org/ghprbhook/', token=None, strict=False):
  81. ghprb_detector = GHPRBHookDetector(hook_user, token, callback_url)
  82. test_repo = ghprb_detector.get_repo(user, repo)
  83. if test_repo:
  84. hooks_ok = ghprb_detector.check_repo_for_access(test_repo, errors, strict=strict)
  85. if hooks_ok:
  86. print('Passed ghprb_detector check for hooks access'
  87. ' for repo [ %s ]' % test_repo.full_name)
  88. return True
  89. else:
  90. print('ERROR: Not enough permissions to setup pull request'
  91. ' builds for repo [ %s ] ' % (test_repo.full_name) +
  92. 'Please see http://wiki.ros.org/buildfarm/Pull%20request%20testing',
  93. file=sys.stderr
  94. )
  95. return False
  96. else:
  97. print(
  98. 'ERROR: No github repository found at %s/%s' % (user, repo),
  99. file=sys.stderr
  100. )
  101. return False
  102. def main():
  103. """A simple main for testing via command line."""
  104. parser = argparse.ArgumentParser(
  105. description='A manual test for ros-pull-request-builder access'
  106. 'to a GitHub repo.')
  107. parser.add_argument('user', type=str)
  108. parser.add_argument('repo', type=str)
  109. parser.add_argument('--callback-url', type=str,
  110. default='http://build.ros.org/ghprbhook/')
  111. parser.add_argument('--hook-user', type=str,
  112. default='ros-pull-request-builder')
  113. parser.add_argument('--password-env', type=str,
  114. default='ROSGHPRB_TOKEN')
  115. args = parser.parse_args()
  116. password = os.getenv(args.password_env)
  117. if not password:
  118. parser.error(
  119. 'OAUTH Token with hook and organization read access'
  120. 'required in ROSGHPRB_TOKEN environment variable')
  121. errors = []
  122. result = check_hooks_on_repo(
  123. args.user,
  124. args.repo,
  125. errors,
  126. args.hook_user,
  127. args.callback_url,
  128. password)
  129. if errors:
  130. print('Errors detected:', file=sys.stderr)
  131. for e in errors:
  132. print(e, file=sys.stderr)
  133. if result:
  134. return 0
  135. return 1
  136. if __name__ == '__main__':
  137. sys.exit(main())