9 from datetime import datetime
10 from collections import defaultdict
13 github_auth_username = os.environ['ICINGA_GITHUB_AUTH_USERNAME']
15 print "ERROR: Environment variable 'ICINGA_GITHUB_AUTH_USERNAME' is not set."
19 github_auth_token = os.environ['ICINGA_GITHUB_AUTH_TOKEN']
21 print "ERROR: Environment variable 'ICINGA_GITHUB_AUTH_TOKEN' is not set."
25 project_name = os.environ['ICINGA_GITHUB_PROJECT']
27 print "ERROR: Environment variable 'ICINGA_GITHUB_PROJECT' is not set."
30 changelog_file = "CHANGELOG.md" # TODO: config param
33 ignored_labels = ["high", "low", "bug", "enhancement", "feedback", "question", "backported"]
35 #################################
38 def write_changelog(line):
39 clfp.write(line + "\n")
45 def fetch_github_resources(uri, params = {}):
48 url = 'https://api.github.com/repos/' + project_name + uri + "?per_page=100" # 100 is the maximum
51 log(2, "Requesting URL: " + url)
52 resp = requests.get(url, auth=(github_auth_username, github_auth_token), params=params)
54 resp.raise_for_status()
55 except requests.exceptions.HTTPError as e:
63 resources.extend(data)
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)
71 break # no link found, we are done
72 log(2, "No more pages to fetch, stop.")
76 def issue_type(issue):
77 if "bug" in [label["name"] for label in issue["labels"]]:
79 elif "enhancement" in [label["name"] for label in issue["labels"]]:
84 def escape_markdown(text):
85 #tmp = text.replace('&', '&').replace('<', '<').replace('>', '>')
87 tmp.replace('\\', '\\\\')
89 return re.sub("([<>*_()\[\]#])", r"\\\1", tmp)
91 def format_labels(issue):
92 labels = filter(lambda label: label not in ignored_labels, [label["name"] for label in issue["labels"]])
95 return " (" + ", ".join(labels) + ")"
99 def format_title(title):
102 issue_title = str(title.encode('ascii', 'ignore').encode('utf-8'))
104 log(1, "Error: Cannot convert " + title + " to UTF-8")
106 # Remove dev.icinga.com tag
107 issue_title = re.sub('\[dev\.icinga\.com #\d+\] ', '', issue_title)
109 #log(1, "Issue title: " + issue_title + "Type: " + str(type(issue_title)))
111 return escape_markdown(issue_title)
113 #################################
117 issues = defaultdict(lambda: defaultdict(list))
119 log(1, "Fetching data from GitHub API for project " + project_name)
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)
128 clfp = open(changelog_file, "w+")
130 with open('tickets.pickle', 'wb') as fp:
131 pickle.dump(tickets, fp)
133 with open('tickets.pickle', 'rb') as fp:
134 cached_issues = pickle.load(fp)
136 for issue in cached_issues: #fetch_github_resources("/issues", { "state": "all" }):
137 milestone = issue["milestone"]
142 ms_title = milestone["title"]
144 if not re.match('^\d+\.\d+\.\d+$', ms_title):
147 if ms_title.split(".")[0] != "2":
150 milestones[ms_title] = milestone
152 ms_tickets = issues[ms_title][issue_type(issue)]
153 ms_tickets.append(issue)
155 # TODO: Generic header based on project_name
156 write_changelog("# Icinga 2.x CHANGELOG")
159 for milestone in sorted(milestones.values(), key=lambda ms: (ms["due_on"], ms["title"]), reverse=True):
160 if milestone["state"] != "closed":
163 if milestone["due_on"] == None:
164 print "Milestone", milestone["title"], "does not have a due date."
167 ms_due_on = datetime.strptime(milestone["due_on"], "%Y-%m-%dT%H:%M:%SZ")
169 write_changelog("## %s (%s)" % (milestone["title"], ms_due_on.strftime("%Y-%m-%d")))
172 ms_description = milestone["description"]
173 ms_description = re.sub('\r\n', '\n', ms_description)
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
178 for category in ["Enhancement", "Bug", "Support"]:
180 ms_issues = issues[milestone["title"]][category]
184 if len(ms_issues) == 0:
187 write_changelog("### " + category)
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"]))
197 log(1, "Finished writing " + changelog_file)