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