blob: 042806f264f670550a2cfd23070c50302fef48fa [file] [log] [blame]
Anita Kunobf3a97b2013-02-28 10:09:46 -05001#!/usr/bin/env python
2# Copyright (c) 2013 Chmouel Boudjnah, eNovance
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16# This script is designed to generate rss feeds for subscription from updates
17# to various gerrit tracked projects. It is intended to be run periodically,
Anita Kuno96a159d2013-03-12 14:14:38 -040018# for example hourly via cron. It takes an optional argument to specify the
19# path to a configuration file.
Anita Kunobf3a97b2013-02-28 10:09:46 -050020# -*- encoding: utf-8 -*-
21__author__ = "Chmouel Boudjnah <chmouel@chmouel.com>"
22import json
23import datetime
24import os
25import sys
26import time
27import cStringIO
28import ConfigParser
29import urllib
30
31import PyRSS2Gen
32
33PROJECTS = ['openstack/nova', 'openstack/keystone', 'opensack/swift']
34JSON_URL = 'https://review.openstack.org/query'
35DEBUG = False
36OUTPUT_MODE = 'multiple'
37
38curdir = os.path.dirname(os.path.realpath(sys.argv[0]))
39
40
41class ConfigurationError(Exception):
42 pass
43
44
45def get_config(config, section, option, default=None):
46 if not config.has_section(section):
47 raise ConfigurationError("Invalid configuration, missing section: %s" %
48 section)
49 if config.has_option(section, option):
50 return config.get(section, option)
51 elif not default is None:
52 return default
53 else:
54 raise ConfigurationError("Invalid configuration, missing "
55 "section/option: %s/%s" % (section, option))
56
57
58def parse_ini(inifile):
59 ret = {}
60 if not os.path.exists(inifile):
61 return
62 config = ConfigParser.RawConfigParser(allow_no_value=True)
63 config.read(inifile)
64
65 if config.has_section('swift'):
66 ret['swift'] = dict(config.items('swift'))
67
68 ret['projects'] = get_config(config, 'general', 'projects', PROJECTS)
69 if type(ret['projects']) is not list:
70 ret['projects'] = [x.strip() for x in ret['projects'].split(',')]
71 ret['json_url'] = get_config(config, 'general', 'json_url', JSON_URL)
72 ret['debug'] = get_config(config, 'general', 'debug', DEBUG)
73 ret['output_mode'] = get_config(config, 'general', 'output_mode',
74 OUTPUT_MODE)
75 return ret
76
Anita Kuno96a159d2013-03-12 14:14:38 -040077try:
78 conffile = sys.argv[1]
79except IndexError:
80 conffile = os.path.join(curdir, '..', 'config', 'openstackwatch.ini')
81CONFIG = parse_ini(conffile)
Anita Kunobf3a97b2013-02-28 10:09:46 -050082
83
84def debug(msg):
85 if DEBUG:
86 print msg
87
88
89def get_javascript(project=None):
Anita Kunoc8cc7a52013-04-10 21:32:08 -040090 url = CONFIG['json_url']
Anita Kunobf3a97b2013-02-28 10:09:46 -050091 if project:
92 url += "+project:" + project
93 fp = urllib.urlretrieve(url)
94 ret = open(fp[0]).read()
95 return ret
96
97
98def parse_javascript(javascript):
99 for row in javascript.splitlines():
100 try:
101 json_row = json.loads(row)
102 except(ValueError):
103 continue
104 if not json_row or not 'project' in json_row or \
105 json_row['project'] not in CONFIG['projects']:
106 continue
107 yield json_row
108
109
110def upload_rss(xml, output_object):
111 if not 'swift' in CONFIG:
112 print xml
113 return
114
115 import swiftclient
116 cfg = CONFIG['swift']
117 client = swiftclient.Connection(cfg['auth_url'],
118 cfg['username'],
119 cfg['password'],
120 auth_version=cfg.get('auth_version',
121 '2.0'))
122 try:
123 client.get_container(cfg['container'])
124 except(swiftclient.client.ClientException):
125 client.put_container(cfg['container'])
126 # eventual consistenties
127 time.sleep(1)
128
129 client.put_object(cfg['container'], output_object,
130 cStringIO.StringIO(xml))
131
132
133def generate_rss(javascript, project=""):
134 title = "OpenStack %s watch RSS feed" % (project)
135 rss = PyRSS2Gen.RSS2(
136 title=title,
137 link="http://github.com/chmouel/openstackwatch.rss",
138 description="The latest reviews about Openstack, straight "
139 "from Gerrit.",
140 lastBuildDate=datetime.datetime.now()
141 )
142 for row in parse_javascript(javascript):
143 author = row['owner']['name']
144 author += " <%s>" % ('email' in row['owner'] and
145 row['owner']['email']
146 or row['owner']['username'])
147 rss.items.append(
148 PyRSS2Gen.RSSItem(
149 title="%s [%s]: %s" % (os.path.basename(row['project']),
150 row['status'],
151 row['subject']),
152 author=author,
153 link=row['url'],
154 guid=PyRSS2Gen.Guid(row['id']),
155 description=row['subject'],
156 pubDate=datetime.datetime.fromtimestamp(row['lastUpdated']),
157 ))
158 return rss.to_xml()
159
160
161def main():
162 if CONFIG['output_mode'] == "combined":
163 upload_rss(generate_rss(get_javascript()),
164 CONFIG['swift']['combined_output_object'])
165 elif CONFIG['output_mode'] == "multiple":
166 for project in CONFIG['projects']:
167 upload_rss(
168 generate_rss(get_javascript(project), project=project),
169 "%s.xml" % (os.path.basename(project)))
170
171if __name__ == '__main__':
172 main()