summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2010-03-04 17:04:50 -0800
committerRuss Cox <rsc@golang.org>2010-03-04 17:04:50 -0800
commit129633b0a8c9a014d0863014057007d410c48e9e (patch)
tree88d062d8eefe423148c1bbc38ebe0e377955fdd8 /misc
parentee111b58c931d0c14dec04c7c8c4bd4f31ade283 (diff)
downloadgolang-129633b0a8c9a014d0863014057007d410c48e9e.tar.gz
goinstall: an experiment in (external) package installation
R=adg, r CC=cw, golang-dev http://codereview.appspot.com/224043
Diffstat (limited to 'misc')
-rw-r--r--misc/dashboard/godashboard/app.yaml3
-rw-r--r--misc/dashboard/godashboard/package.py132
2 files changed, 135 insertions, 0 deletions
diff --git a/misc/dashboard/godashboard/app.yaml b/misc/dashboard/godashboard/app.yaml
index ec4d8d9c1..1c786a6c1 100644
--- a/misc/dashboard/godashboard/app.yaml
+++ b/misc/dashboard/godashboard/app.yaml
@@ -4,5 +4,8 @@ runtime: python
api_version: 1
handlers:
+- url: /package.*
+ script: package.py
+
- url: /.*
script: gobuild.py
diff --git a/misc/dashboard/godashboard/package.py b/misc/dashboard/godashboard/package.py
new file mode 100644
index 000000000..351a1fadc
--- /dev/null
+++ b/misc/dashboard/godashboard/package.py
@@ -0,0 +1,132 @@
+# Copyright 2010 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+# This is the server part of the package dashboard.
+# It must be run by App Engine.
+
+from google.appengine.api import memcache
+from google.appengine.runtime import DeadlineExceededError
+from google.appengine.ext import db
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import template
+from google.appengine.ext.webapp.util import run_wsgi_app
+import binascii
+import datetime
+import hashlib
+import hmac
+import logging
+import os
+import re
+import struct
+import time
+import urllib2
+
+# Storage model for package info recorded on server.
+# Just path, count, and time of last install.
+class Package(db.Model):
+ path = db.StringProperty()
+ web_url = db.StringProperty() # derived from path
+ count = db.IntegerProperty()
+ last_install = db.DateTimeProperty()
+
+re_bitbucket = re.compile(r'^bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+$')
+re_googlecode = re.compile(r'^[a-z0-9\-]+\.googlecode\.com/(svn|hg)$')
+re_github = re.compile(r'^github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+$')
+
+MaxPathLength = 100
+
+class PackagePage(webapp.RequestHandler):
+ def get(self):
+ if self.request.get('fmt') == 'json':
+ return self.json()
+
+ q = Package.all()
+ q.order('-last_install')
+ by_time = q.fetch(100)
+
+ q = Package.all()
+ q.order('-count')
+ by_count = q.fetch(100)
+
+ self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
+ path = os.path.join(os.path.dirname(__file__), 'package.html')
+ self.response.out.write(template.render(path, {"by_time": by_time, "by_count": by_count}))
+
+ def json(self):
+ self.response.set_status(200)
+ self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
+ q = Package.all()
+ s = '{"packages": ['
+ sep = ''
+ for r in q.fetch(1000):
+ s += '%s\n\t{"path": "%s", "last_install": "%s", "count": "%s"}' % (sep, r.path, r.last_install, r.count)
+ sep = ','
+ s += '\n]}\n'
+ self.response.out.write(s)
+
+ def can_get_url(self, url):
+ try:
+ req = urllib2.Request(url)
+ response = urllib2.urlopen(req)
+ return True
+ except:
+ return False
+
+ def is_valid_package_path(self, path):
+ return (re_bitbucket.match(path) or
+ re_googlecode.match(path) or
+ re_github.match(path))
+
+ def record_pkg(self, path):
+ # sanity check string
+ if not path or len(path) > MaxPathLength or not self.is_valid_package_path(path):
+ return False
+
+ # look in datastore
+ key = 'pkg-' + path
+ p = Package.get_by_key_name(key)
+ if p is None:
+ # not in datastore - verify URL before creating
+ if re_bitbucket.match(path):
+ check_url = 'http://' + path + '/?cmd=heads'
+ web = 'http://' + path + '/'
+ elif re_github.match(path):
+ # github doesn't let you fetch the .git directory anymore.
+ # fetch .git/info/refs instead, like git clone would.
+ check_url = 'http://'+path+'.git/info/refs'
+ web = 'http://' + path
+ elif re_googlecode.match(path):
+ check_url = 'http://'+path
+ web = 'http://code.google.com/p/' + path[:path.index('.')]
+ else:
+ logging.error('unrecognized path: %s', path)
+ return False
+ if not self.can_get_url(check_url):
+ logging.error('cannot get %s', check_url)
+ return False
+ p = Package(key_name = key, path = path, count = 0, web_url = web)
+
+ # update package object
+ p.count += 1
+ p.last_install = datetime.datetime.utcnow()
+ p.put()
+ return True
+
+ def post(self):
+ path = self.request.get('path')
+ ok = self.record_pkg(path)
+ if ok:
+ self.response.set_status(200)
+ self.response.out.write('ok')
+ else:
+ logging.error('invalid path in post: %s', path)
+ self.response.set_status(500)
+ self.response.out.write('not ok')
+
+def main():
+ app = webapp.WSGIApplication([('/package', PackagePage)], debug=True)
+ run_wsgi_app(app)
+
+if __name__ == '__main__':
+ main()