]> granicus.if.org Git - icinga2/blob - changelog.py
Make default getters and setters non-virtual
[icinga2] / changelog.py
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3
4 import requests
5 import re
6 import pickle
7 import sys
8 import os
9 from datetime import datetime
10 from collections import defaultdict
11
12 try:
13     github_auth_username = os.environ['ICINGA_GITHUB_AUTH_USERNAME']
14 except KeyError:
15     print "ERROR: Environment variable 'ICINGA_GITHUB_AUTH_USERNAME' is not set."
16     sys.exit(1)
17
18 try:
19     github_auth_token = os.environ['ICINGA_GITHUB_AUTH_TOKEN']
20 except:
21     print "ERROR: Environment variable 'ICINGA_GITHUB_AUTH_TOKEN' is not set."
22     sys.exit(1)
23
24 try:
25     project_name = os.environ['ICINGA_GITHUB_PROJECT']
26 except:
27     print "ERROR: Environment variable 'ICINGA_GITHUB_PROJECT' is not set."
28     sys.exit(1)
29
30 changelog_file = "CHANGELOG.md" # TODO: config param
31 debug = 1
32
33 ignored_labels = ["high", "low", "bug", "enhancement", "feedback", "question", "backported"]
34
35 #################################
36 ## Helpers
37
38 def write_changelog(line):
39     clfp.write(line + "\n")
40
41 def log(level, msg):
42     if level <= debug:
43         print " " + msg
44
45 def fetch_github_resources(uri, params = {}):
46     resources = []
47
48     url = 'https://api.github.com/repos/' + project_name + uri + "?per_page=100" # 100 is the maximum
49
50     while True:
51         log(2, "Requesting URL: " + url)
52         resp = requests.get(url, auth=(github_auth_username, github_auth_token), params=params)
53         try:
54             resp.raise_for_status()
55         except requests.exceptions.HTTPError as e:
56             raise e
57
58         data = resp.json()
59
60         if len(data) == 0:
61             break
62
63         resources.extend(data)
64
65         # fetch the next page from headers, do not count pages
66         # http://engineering.hackerearth.com/2014/08/21/python-requests-module/
67         if "next" in resp.links:
68             url = resp.links['next']['url']
69             log(2, "Found next link for Github pagination: " + url)
70         else:
71             break # no link found, we are done
72             log(2, "No more pages to fetch, stop.")
73
74     return resources
75
76 def issue_type(issue):
77     if "bug" in [label["name"] for label in issue["labels"]]:
78         return "Bug"
79     elif "enhancement" in [label["name"] for label in issue["labels"]]:
80         return "Enhancement"
81     else:
82         return "Support"
83
84 def escape_markdown(text):
85     #tmp = text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
86     tmp = text
87     tmp.replace('\\', '\\\\')
88
89     return re.sub("([<>*_()\[\]#])", r"\\\1", tmp)
90
91 def format_labels(issue):
92     labels = filter(lambda label: label not in ignored_labels, [label["name"] for label in issue["labels"]])
93
94     if len(labels):
95         return " (" + ", ".join(labels) + ")"
96     else:
97         return ""
98
99 def format_title(title):
100     # Fix encoding
101     try:
102         issue_title = str(title.encode('ascii', 'ignore').encode('utf-8'))
103     except Error:
104         log(1, "Error: Cannot convert " + title + " to UTF-8")
105
106     # Remove dev.icinga.com tag
107     issue_title = re.sub('\[dev\.icinga\.com #\d+\] ', '', issue_title)
108
109     #log(1, "Issue title: " + issue_title + "Type: " + str(type(issue_title)))
110
111     return escape_markdown(issue_title)
112
113 #################################
114 ## MAIN
115
116 milestones = {}
117 issues = defaultdict(lambda: defaultdict(list))
118
119 log(1, "Fetching data from GitHub API for project " + project_name)
120
121 try:
122     tickets = fetch_github_resources("/issues", { "state": "all" })
123 except requests.exceptions.HTTPError as e:
124     log(1, "ERROR " + str(e.response.status_code) + ": " + e.response.text)
125
126     sys.exit(1)
127
128 clfp = open(changelog_file, "w+")
129
130 with open('tickets.pickle', 'wb') as fp:
131     pickle.dump(tickets, fp)
132
133 with open('tickets.pickle', 'rb') as fp:
134     cached_issues = pickle.load(fp)
135
136 for issue in cached_issues: #fetch_github_resources("/issues", { "state": "all" }):
137     milestone = issue["milestone"]
138
139     if not milestone:
140         continue
141
142     ms_title = milestone["title"]
143
144     if not re.match('^\d+\.\d+\.\d+$', ms_title):
145         continue
146
147     if ms_title.split(".")[0] != "2":
148         continue
149
150     milestones[ms_title] = milestone
151
152     ms_tickets = issues[ms_title][issue_type(issue)]
153     ms_tickets.append(issue)
154
155 # TODO: Generic header based on project_name
156 write_changelog("# Icinga 2.x CHANGELOG")
157 write_changelog("")
158
159 for milestone in sorted(milestones.values(), key=lambda ms: (ms["due_on"], ms["title"]), reverse=True):
160     if milestone["state"] != "closed":
161         continue
162
163     if milestone["due_on"] == None:
164         print "Milestone", milestone["title"], "does not have a due date."
165         sys.exit(1)
166
167     ms_due_on = datetime.strptime(milestone["due_on"], "%Y-%m-%dT%H:%M:%SZ")
168
169     write_changelog("## %s (%s)" % (milestone["title"], ms_due_on.strftime("%Y-%m-%d")))
170     write_changelog("")
171
172     ms_description = milestone["description"]
173     ms_description = re.sub('\r\n', '\n', ms_description)
174
175     if len(ms_description) > 0:
176         write_changelog("### Notes\n\n" + ms_description + "\n") # Don't escape anything, we take care on Github for valid Markdown
177
178     for category in ["Enhancement", "Bug", "Support"]:
179         try:
180             ms_issues = issues[milestone["title"]][category]
181         except KeyError:
182             continue
183
184         if len(ms_issues) == 0:
185             continue
186
187         write_changelog("### " + category)
188         write_changelog("")
189
190         for issue in ms_issues:
191             write_changelog("* [#" + str(issue["number"]) + "](https://github.com/" + project_name
192                 + "/issues/" + str(issue["number"]) + ")" + format_labels(issue) + ": " + format_title(issue["title"]))
193
194         write_changelog("")
195
196 clfp.close()
197 log(1, "Finished writing " + changelog_file)