Adding openstackwatch rss functionality to be available to gerrit.

Adding openstackwatch.py to the cmd directory.
Creating directory jeepyb/config for the openstackwatch.ini file.
Adding the openstackwatch.ini-sample file.

Fixes bug #1136069

Change-Id: Icc5d41d10d893e0b7ed048d99cfcb9c5e7f91f30
Reviewed-on: https://review.openstack.org/23176
Reviewed-by: Jeremy Stanley <fungi@yuggoth.org>
Reviewed-by: James E. Blair <corvus@inaugust.com>
Approved: James E. Blair <corvus@inaugust.com>
Tested-by: Jenkins
diff --git a/jeepyb/cmd/openstackwatch.py b/jeepyb/cmd/openstackwatch.py
new file mode 100644
index 0000000..1ba7799
--- /dev/null
+++ b/jeepyb/cmd/openstackwatch.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 Chmouel Boudjnah, eNovance
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# This script is designed to generate rss feeds for subscription from updates
+# to various gerrit tracked projects. It is intended to be run periodically,
+# for example hourly via cron.
+# -*- encoding: utf-8 -*-
+__author__ = "Chmouel Boudjnah <chmouel@chmouel.com>"
+import json
+import datetime
+import os
+import sys
+import time
+import cStringIO
+import ConfigParser
+import urllib
+
+import PyRSS2Gen
+
+PROJECTS = ['openstack/nova', 'openstack/keystone', 'opensack/swift']
+JSON_URL = 'https://review.openstack.org/query'
+DEBUG = False
+OUTPUT_MODE = 'multiple'
+
+curdir = os.path.dirname(os.path.realpath(sys.argv[0]))
+
+
+class ConfigurationError(Exception):
+    pass
+
+
+def get_config(config, section, option, default=None):
+    if not config.has_section(section):
+        raise ConfigurationError("Invalid configuration, missing section: %s" %
+                                 section)
+    if config.has_option(section, option):
+        return config.get(section, option)
+    elif not default is None:
+        return default
+    else:
+        raise ConfigurationError("Invalid configuration, missing "
+                                 "section/option: %s/%s" % (section, option))
+
+
+def parse_ini(inifile):
+    ret = {}
+    if not os.path.exists(inifile):
+        return
+    config = ConfigParser.RawConfigParser(allow_no_value=True)
+    config.read(inifile)
+
+    if config.has_section('swift'):
+        ret['swift'] = dict(config.items('swift'))
+
+    ret['projects'] = get_config(config, 'general', 'projects', PROJECTS)
+    if type(ret['projects']) is not list:
+        ret['projects'] = [x.strip() for x in ret['projects'].split(',')]
+    ret['json_url'] = get_config(config, 'general', 'json_url', JSON_URL)
+    ret['debug'] = get_config(config, 'general', 'debug', DEBUG)
+    ret['output_mode'] = get_config(config, 'general', 'output_mode',
+                                    OUTPUT_MODE)
+    return ret
+
+CONFIG = parse_ini(os.path.join(curdir, '..', 'config', 'openstackwatch.ini'))
+
+
+def debug(msg):
+    if DEBUG:
+        print msg
+
+
+def get_javascript(project=None):
+    url = "%s?q=status:open" % CONFIG['json_url']
+    if project:
+        url += "+project:" + project
+    fp = urllib.urlretrieve(url)
+    ret = open(fp[0]).read()
+    return ret
+
+
+def parse_javascript(javascript):
+    for row in javascript.splitlines():
+        try:
+            json_row = json.loads(row)
+        except(ValueError):
+            continue
+        if not json_row or not 'project' in json_row or \
+                json_row['project'] not in CONFIG['projects']:
+            continue
+        yield json_row
+
+
+def upload_rss(xml, output_object):
+    if not 'swift' in CONFIG:
+        print xml
+        return
+
+    import swiftclient
+    cfg = CONFIG['swift']
+    client = swiftclient.Connection(cfg['auth_url'],
+                                    cfg['username'],
+                                    cfg['password'],
+                                    auth_version=cfg.get('auth_version',
+                                                         '2.0'))
+    try:
+        client.get_container(cfg['container'])
+    except(swiftclient.client.ClientException):
+        client.put_container(cfg['container'])
+        # eventual consistenties
+        time.sleep(1)
+
+    client.put_object(cfg['container'], output_object,
+                      cStringIO.StringIO(xml))
+
+
+def generate_rss(javascript, project=""):
+    title = "OpenStack %s watch RSS feed" % (project)
+    rss = PyRSS2Gen.RSS2(
+        title=title,
+        link="http://github.com/chmouel/openstackwatch.rss",
+        description="The latest reviews about Openstack, straight "
+                    "from Gerrit.",
+        lastBuildDate=datetime.datetime.now()
+    )
+    for row in parse_javascript(javascript):
+        author = row['owner']['name']
+        author += " <%s>" % ('email' in row['owner'] and
+                             row['owner']['email']
+                             or row['owner']['username'])
+        rss.items.append(
+            PyRSS2Gen.RSSItem(
+                title="%s [%s]: %s" % (os.path.basename(row['project']),
+                                       row['status'],
+                                       row['subject']),
+                author=author,
+                link=row['url'],
+                guid=PyRSS2Gen.Guid(row['id']),
+                description=row['subject'],
+                pubDate=datetime.datetime.fromtimestamp(row['lastUpdated']),
+            ))
+    return rss.to_xml()
+
+
+def main():
+    if CONFIG['output_mode'] == "combined":
+        upload_rss(generate_rss(get_javascript()),
+                   CONFIG['swift']['combined_output_object'])
+    elif CONFIG['output_mode'] == "multiple":
+        for project in CONFIG['projects']:
+            upload_rss(
+                generate_rss(get_javascript(project), project=project),
+                "%s.xml" % (os.path.basename(project)))
+
+if __name__ == '__main__':
+    main()
diff --git a/jeepyb/config/openstackwatch.ini-sample b/jeepyb/config/openstackwatch.ini-sample
new file mode 100644
index 0000000..d8544e6
--- /dev/null
+++ b/jeepyb/config/openstackwatch.ini-sample
@@ -0,0 +1,39 @@
+# -*- Mode: conf -*-
+
+[general]
+# only show certain projects (don't forget the openstack/ as start)
+projects = openstack/swift, openstack/cinder
+
+# The Json URL where is the gerrit system.
+json_url = https://review.openstack.org/query?q=status:open
+
+# Allow different mode to output to swift, by default 'combined' will
+# combined all rss in one and 'multiple' will upload all the projects
+# in each rss file.
+mode = combined
+
+# username to your swift cluster
+[swift]
+# username/tenant for swift with 2.0 or just username with 1.0 (i.e:
+# RAX).
+# username =
+
+# passowrd or api key
+# password =
+
+# container to upload (probably want to be public)
+# container =
+
+# auth_url of the cluster, for Rackspace this is :
+#   https://auth.api.rackspacecloud.com/v1.0
+# or Rackspace UK :
+#   https://lon.auth.api.rackspacecloud.com/v1.0
+# auth_url = https://lon.auth.api.rackspacecloud.com/v1.0
+
+# auth version (1.0 for Rackspace clouds, 2.0 for keystone backend clusters)
+# auth_version = 1.0
+
+# the object name where to store the combined rss
+# combined_output_object = openstackwatch.xml
+
+# vim: ft=dosini