github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/gubernator/github/admin.py (about)

     1  # Copyright 2016 The Kubernetes Authors.
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #     http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  
    15  import collections
    16  import cPickle as pickle
    17  import logging
    18  import os
    19  
    20  import webapp2
    21  
    22  from google.appengine.api import urlfetch
    23  from google.appengine.ext import deferred
    24  from google.appengine.ext import ndb
    25  
    26  import models
    27  import handlers
    28  
    29  # ndb model.query likes to use == True
    30  # pylint: disable=singleton-comparison
    31  
    32  class RecomputeOpenPRs(object):
    33      keys_only = True
    34  
    35      @staticmethod
    36      def query():
    37          return models.GHIssueDigest.query(
    38              models.GHIssueDigest.is_open == True,
    39              models.GHIssueDigest.is_pr == True
    40          )
    41  
    42      @staticmethod
    43      def handle_entity(entity):
    44          repo, number = entity.id().split(' ')
    45          handlers.update_issue_digest(repo, number, always_put=True)
    46          return {'puts': 1}
    47  
    48  @ndb.toplevel
    49  def migrate(migration, cursor=None, last_parent=None, stop=False):
    50      entities, next_cursor, more = migration.query().fetch_page(
    51          10, start_cursor=cursor, keys_only=migration.keys_only)
    52  
    53      counters = collections.Counter()
    54  
    55      for entity in entities:
    56          changes = migration.handle_entity(entity)
    57          counters.update(changes)
    58  
    59      summary = ', '.join('%s: %d' % x for x in sorted(counters.items()))
    60      if entities:
    61          logging.info('fetched %d. %s. (%r-%r)',
    62                       len(entities), summary, entities[0], entities[-1])
    63  
    64      if stop:
    65          return
    66  
    67      if more and next_cursor:
    68          deferred.defer(migrate, migration, cursor=next_cursor, last_parent=last_parent)
    69  
    70  
    71  class Digest(webapp2.RequestHandler):
    72      def get(self):
    73          results = models.GHIssueDigest.query(
    74              models.GHIssueDigest.is_open == True)
    75          self.response.headers['content-type'] = 'text/plain'
    76          self.response.write(pickle.dumps(list(results), pickle.HIGHEST_PROTOCOL))
    77  
    78  
    79  class AdminDash(webapp2.RequestHandler):
    80      def get(self):
    81          self.response.write("""
    82  <form action="/admin/reprocess" method="post">
    83  <button>Reprocess Open Issues/PRs</button><input type="checkbox" name="background">Background
    84  </form>
    85  <form action="/admin/digest_sync" method="post">
    86  <button>Download GHIssueDigest from production</button>
    87  </form>
    88          """)
    89  
    90      def check_csrf(self):
    91          # https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
    92          #     #Checking_The_Referer_Header
    93          origin = self.request.headers.get('origin') + '/'
    94          expected = self.request.host_url + '/'
    95          if not (origin and origin == expected):
    96              logging.error('csrf check failed for %s, origin: %r', self.request.url, origin)
    97              self.abort(403)
    98  
    99  
   100  class Reprocessor(AdminDash):
   101      def post(self):
   102          self.check_csrf()
   103          migration = RecomputeOpenPRs()
   104          if self.request.get('background'):
   105              deferred.defer(migrate, migration)
   106              self.response.write('running.')
   107          else:
   108              migrate(migration, stop=True)
   109  
   110  
   111  class DigestSync(AdminDash):
   112      def post(self):
   113          if not os.environ['SERVER_SOFTWARE'].startswith('Development/'):
   114              self.abort(400)
   115          # For local development, download GHIssueDigests from the production
   116          # server.
   117          result = urlfetch.fetch(
   118              'https://github-dot-k8s-gubernator.appspot.com/digest', deadline=60)
   119          if result.status_code != 200:
   120              self.abort(result.status_code)
   121          body = result.content
   122          self.response.headers['content-type'] = 'text/plain'
   123          self.response.write('%s\n' % len(body))
   124          self.response.write(repr(body[:8]))
   125          results = pickle.loads(body)
   126          for res in results:
   127              res.key = ndb.Key(models.GHIssueDigest, res.key.id())
   128              self.response.write('%s\n' % res.key)
   129              res.put()
   130  
   131  
   132  app = webapp2.WSGIApplication([
   133      (r'/digest', Digest),
   134      (r'/admin/?', AdminDash),
   135      (r'/admin/reprocess', Reprocessor),
   136      (r'/admin/digest_sync', DigestSync),
   137  ], debug=True)