generator.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. from __future__ import absolute_import
  2. import os
  3. import zlib
  4. import json
  5. import click
  6. import logging
  7. import six
  8. from datetime import datetime
  9. from subprocess import Popen, PIPE
  10. from contextlib import contextmanager
  11. from six.moves.urllib.parse import urlparse
  12. HERE = os.path.abspath(os.path.dirname(__file__))
  13. SENTRY_CONFIG = os.environ['SENTRY_CONF'] = os.path.join(HERE, 'sentry.conf.py')
  14. os.environ['SENTRY_SKIP_BACKEND_VALIDATION'] = '1'
  15. # No sentry or django imports before that point
  16. from sentry.runner import configure
  17. configure()
  18. from django.conf import settings
  19. # Fair game from here
  20. from django.core.management import call_command
  21. from sentry.utils.apidocs import Runner, MockUtils, iter_scenarios, \
  22. iter_endpoints, get_sections
  23. OUTPUT_PATH = os.path.join(HERE, 'cache')
  24. HOST = urlparse(settings.SENTRY_OPTIONS['system.url-prefix']).netloc
  25. # We don't care about you, go away
  26. _logger = logging.getLogger('sentry.events')
  27. _logger.disabled = True
  28. def color_for_string(s):
  29. colors = ('red', 'green', 'yellow', 'blue', 'cyan', 'magenta')
  30. return colors[zlib.crc32(s) % len(colors)]
  31. def report(category, message, fg=None):
  32. if fg is None:
  33. fg = color_for_string(category)
  34. click.echo('[%s] %s: %s' % (
  35. six.text_type(datetime.utcnow()).split('.')[0],
  36. click.style(category, fg=fg),
  37. message
  38. ))
  39. def launch_redis():
  40. report('redis', 'Launching redis server')
  41. cl = Popen(['redis-server', '-'], stdin=PIPE, stdout=open(os.devnull, 'r+'))
  42. cl.stdin.write('''
  43. port %(port)s
  44. databases %(databases)d
  45. save ""
  46. ''' % {
  47. 'port': six.text_type(settings.SENTRY_APIDOCS_REDIS_PORT),
  48. 'databases': 4,
  49. })
  50. cl.stdin.flush()
  51. cl.stdin.close()
  52. return cl
  53. def spawn_sentry():
  54. report('sentry', 'Launching sentry server')
  55. cl = Popen(['sentry', '--config=' + SENTRY_CONFIG, 'run', 'web',
  56. '-w', '1', '--bind', '127.0.0.1:%s' % settings.SENTRY_APIDOCS_WEB_PORT])
  57. return cl
  58. @contextmanager
  59. def management_connection():
  60. from sqlite3 import connect
  61. cfg = settings.DATABASES['default']
  62. con = connect(cfg['NAME'])
  63. try:
  64. con.cursor()
  65. yield con
  66. finally:
  67. con.close()
  68. def init_db():
  69. drop_db()
  70. report('db', 'Migrating database (this can time some time)')
  71. call_command('syncdb', migrate=True, interactive=False,
  72. traceback=True, verbosity=0)
  73. def drop_db():
  74. report('db', 'Dropping database')
  75. try:
  76. os.remove(settings.DATABASES['default']['NAME'])
  77. except (OSError, IOError):
  78. pass
  79. class SentryBox(object):
  80. def __init__(self):
  81. self.redis = None
  82. self.sentry = None
  83. self.task_runner = None
  84. def __enter__(self):
  85. self.redis = launch_redis()
  86. self.sentry = spawn_sentry()
  87. init_db()
  88. return self
  89. def __exit__(self, exc_type, exc_value, tb):
  90. drop_db()
  91. if self.redis is not None:
  92. report('redis', 'Stopping redis server')
  93. self.redis.kill()
  94. self.redis.wait()
  95. if self.sentry is not None:
  96. report('sentry', 'Shutting down sentry server')
  97. self.sentry.kill()
  98. self.sentry.wait()
  99. def dump_json(path, data):
  100. path = os.path.join(OUTPUT_PATH, path)
  101. try:
  102. os.makedirs(os.path.dirname(path))
  103. except OSError:
  104. pass
  105. with open(path, 'w') as f:
  106. for line in json.dumps(data, indent=2, sort_keys=True).splitlines():
  107. f.write(line.rstrip() + '\n')
  108. def run_scenario(vars, scenario_ident, func):
  109. runner = Runner(scenario_ident, func, **vars)
  110. report('scenario', 'Running scenario "%s"' % scenario_ident)
  111. func(runner)
  112. dump_json('scenarios/%s.json' % scenario_ident, runner.to_json())
  113. @click.command()
  114. @click.option('--output-path', type=click.Path())
  115. def cli(output_path):
  116. """API docs dummy generator."""
  117. global OUTPUT_PATH
  118. if output_path is not None:
  119. OUTPUT_PATH = os.path.abspath(output_path)
  120. with SentryBox():
  121. utils = MockUtils()
  122. report('org', 'Creating user and organization')
  123. user = utils.create_user('john@interstellar.invalid')
  124. org = utils.create_org('The Interstellar Jurisdiction',
  125. owner=user)
  126. api_key = utils.create_api_key(org)
  127. report('org', 'Creating team')
  128. team = utils.create_team('Powerful Abolitionist',
  129. org=org)
  130. projects = []
  131. for project_name in 'Pump Station', 'Prime Mover':
  132. report('project', 'Creating project "%s"' % project_name)
  133. project = utils.create_project(project_name, team=team, org=org)
  134. release = utils.create_release(project=project, user=user)
  135. report('event', 'Creating event for "%s"' % project_name)
  136. event1 = utils.create_event(project=project, release=release,
  137. platform='python')
  138. event2 = utils.create_event(project=project, release=release,
  139. platform='java')
  140. projects.append({
  141. 'project': project,
  142. 'release': release,
  143. 'events': [event1, event2],
  144. })
  145. vars = {
  146. 'org': org,
  147. 'me': user,
  148. 'api_key': api_key,
  149. 'teams': [{
  150. 'team': team,
  151. 'projects': projects,
  152. }],
  153. }
  154. for scenario_ident, func in iter_scenarios():
  155. run_scenario(vars, scenario_ident, func)
  156. section_mapping = {}
  157. report('docs', 'Exporting endpoint documentation')
  158. for endpoint in iter_endpoints():
  159. report('endpoint', 'Exporting docs for "%s"' %
  160. endpoint['endpoint_name'])
  161. section_mapping.setdefault(endpoint['section'], []) \
  162. .append((endpoint['endpoint_name'],
  163. endpoint['title']))
  164. dump_json('endpoints/%s.json' % endpoint['endpoint_name'], endpoint)
  165. report('docs', 'Exporting sections')
  166. dump_json('sections.json', {
  167. 'sections': dict((section, {
  168. 'title': title,
  169. 'entries': dict(section_mapping.get(section, ())),
  170. }) for section, title in six.iteritems(get_sections()))
  171. })
  172. if __name__ == '__main__':
  173. cli()