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