Merge lp:~nskaggs/juju-release-tools/generate-release-notes into lp:juju-release-tools

Proposed by Nicholas Skaggs
Status: Rejected
Rejected by: Nicholas Skaggs
Proposed branch: lp:~nskaggs/juju-release-tools/generate-release-notes
Merge into: lp:juju-release-tools
Diff against target: 297 lines (+293/-0)
1 file modified
generate_release_checklist.py (+293/-0)
To merge this branch: bzr merge lp:~nskaggs/juju-release-tools/generate-release-notes
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Needs Information
Aaron Bentley (community) Needs Fixing
Review via email: mp+320550@code.launchpad.net

Description of the change

Add a script to generate a release checklist. This is initially based around juju, but is generic enough to support other projects. The checklist can be used to gate releases.

To post a comment you must log in.
Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

If someone knows why I can't import assignee from a bug without unicode issues, that would be a welcome improvement on below.

Revision history for this message
Aaron Bentley (abentley) :
review: Needs Fixing
Revision history for this message
Curtis Hovey (sinzui) wrote :

I see you got hold of some of my scripts, but you are missing a few crucial trick to level-up. This script doesn't scale or work for bots, see juju-reports/jujureports/jobs/lptasks.py which is also gave to Chris an Torsten last year, and Several other managers over the last 5 years.

The "display_name" and "title" attributes causes problems for everyone. We often do this for reports

    bugtask.assignee.display_name.encode('ascii', 'ignore')

More inline

review: Needs Information (code)
377. By Nicholas Skaggs

cleanups per review

378. By Nicholas Skaggs

suggested changes from release meeting

379. By Nicholas Skaggs

add stakeholder output

380. By Nicholas Skaggs

add lp credentials for bot use, simplify get_*_bugs

381. By Nicholas Skaggs

add safe_encode, fix tag typo

Unmerged revisions

381. By Nicholas Skaggs

add safe_encode, fix tag typo

380. By Nicholas Skaggs

add lp credentials for bot use, simplify get_*_bugs

379. By Nicholas Skaggs

add stakeholder output

378. By Nicholas Skaggs

suggested changes from release meeting

377. By Nicholas Skaggs

cleanups per review

376. By Nicholas Skaggs

v1, manual updating of CDO and CI results required (sso + vpn requirements)

375. By Nicholas Skaggs

Working version with somewhat poor printing, unicode issue on assignee, flake8 issues, and no details for CI or CDO because of SSO requirements

374. By Nicholas Skaggs

working tags

373. By Nicholas Skaggs

naming

372. By Nicholas Skaggs

first pass

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'generate_release_checklist.py'
2--- generate_release_checklist.py 1970-01-01 00:00:00 +0000
3+++ generate_release_checklist.py 2017-03-28 20:06:32 +0000
4@@ -0,0 +1,293 @@
5+#!/usr/bin/python
6+
7+from __future__ import print_function
8+
9+from argparse import ArgumentParser
10+import sys
11+
12+from launchpadlib.launchpad import Launchpad
13+
14+TEMPLATE = """\
15+Release checklist for {project}-{milestone}
16+{milestone_link}
17+
18+Meeting Attendees:
19+Key StakeHolders: {stakeholders}
20+Meeting Date: {meeting_date}
21+Release Date: {release_date}
22+
23+Test Results:
24+CI: {project_ci_link}
25+{project_ci_results}
26+
27+CDO QA: {cdo_qa_link}
28+
29+Compatibility Matrix:
30+{cdo_qa_results}
31+
32+Main Features of this release:
33+
34+{features}
35+
36+Release Notes Available:
37+
38+{milestone}: {release_notes}
39+
40+Documentation Ready:
41+
42+{docs}
43+
44+Issues Resolved in this milestone: {num_fixed_bugs}
45+All Issues fixed during this release: {all_fixed_bugs}
46+
47+{fixed_bugs}
48+
49+Open Release-Blocking Issues (stable-blocker, rc-blocker): {num_blocker_bugs}
50+
51+{blocker_tag_bugs}
52+
53+Open CDO-Blocking Issues (cdo-qa-blocker): {num_cdo_blocker_bugs}
54+
55+{cdo_blocker_bugs}
56+
57+Open Critical Issues: {num_critical_bugs}
58+
59+{critical_bugs}
60+
61+Other Issues Anyone?
62+"""
63+
64+
65+def _as_list(value):
66+ """Return None or the list."""
67+ if value and not isinstance(value, list):
68+ value = [value]
69+ return value
70+
71+
72+def _check_for_stakeholder_tag(bug):
73+ # tags in use
74+ # Conjure-up: conjure-up
75+ # OIL: oil-2.0, oil {would be nice to settle on one :D}
76+ # CDO QA: cdo-qa
77+ # CPE: cpe, cpe-sa, cpec {would be nice to settle on one :D}
78+ # IS: canonical-is
79+ # Bootstack: canonical-bootstack
80+ # STS: sts
81+ # JAAS: jaas
82+ # Landscape: landscape
83+ # Openstack and UOSCI: uosci
84+ # Juju QA: jujuqa
85+ # Charm Eco: charmers
86+
87+ # proposed tags
88+ # LXD: canonical-lxd
89+ # MAAS: canonical-maas
90+ # Docs: canonical-docs
91+ # Omnibus: omnibus
92+ # GUI: juju-gui
93+ stakeholders = []
94+ stakeholder_tags = ['conjure-up', 'oil-.20', 'oil', 'cdo-qa', 'cpe',
95+ 'cpe-sa', 'cpec', 'canonical-is',
96+ 'canonical-bootstack', 'sts', 'jaas', 'landscape',
97+ 'uosci', 'jujuqa', 'charmers', 'canonical-lxd',
98+ 'canonical-maas', 'canonical-docs', 'omnibus',
99+ 'juju-gui']
100+ for tag in stakeholder_tags:
101+ if tag in bug.tags:
102+ stakeholders.append(tag)
103+
104+ return stakeholders
105+
106+
107+def _get_search_params(status=None, importance=None, tags=None,
108+ omit_duplicates=True):
109+ return dict(status=_as_list(status), importance=_as_list(importance),
110+ tags=_as_list(tags), tags_combinator='Any',
111+ omit_duplicates=omit_duplicates)
112+
113+
114+def _safe_encode(string):
115+ return string.encode('ascii', 'ignore')
116+
117+
118+def _parse_bug_tasks(tasks):
119+ bugs = []
120+ for bugtask in tasks:
121+ bug = bugtask.bug
122+
123+ if bugtask.assignee:
124+ assignee = bugtask.assignee.display_name
125+ else:
126+ assignee = u'Unassigned'
127+
128+ if bugtask.milestone:
129+ milestone = bugtask.milestone.name
130+ else:
131+ milestone = u'None'
132+
133+ stakeholders = _check_for_stakeholder_tag(bugtask.bug)
134+ if not stakeholders:
135+ stakeholders = 'Not a stakeholder bug'
136+ bugs.append((bug.id, bugtask.date_created, bugtask.status,
137+ bugtask.importance, milestone,
138+ _safe_encode(bug.owner.display_name),
139+ _safe_encode(assignee),
140+ _safe_encode(bug.title.capitalize()), stakeholders))
141+ return bugs
142+
143+
144+def get_milestone_bugs(lp_milestone, status=None,
145+ importance=None, tags=None):
146+ """Return a list of bug information."""
147+
148+ params = _get_search_params(status, importance, tags)
149+ tasks = lp_milestone.searchTasks(**params)
150+
151+ return _parse_bug_tasks(tasks)
152+
153+
154+def get_project_bugs(lp_project, status=None, importance=None, tags=None):
155+ """Return a list of bug information"""
156+
157+ params = _get_search_params(status, importance, tags)
158+ tasks = lp_project.searchTasks(**params)
159+
160+ return _parse_bug_tasks(tasks)
161+
162+
163+def format_bug_text(bugs):
164+ """Return the list of bug information as formatted text."""
165+ resolved = []
166+
167+ for bug in bugs:
168+ # Formatting to ensure fixed spacing
169+ status = '{0: <13}'.format(bug[2])
170+ importance = '{0: <10}'.format(bug[3])
171+ milestone = '{0: <10}'.format(bug[4])
172+
173+ # construct generic lp link
174+ lp_link = 'https://launchpad.net/bugs/{}'.format(bug[0])
175+ # Tabs import into google docs better than spaces
176+ resolved.append(u"{}\n{}\t{}\t{}\t{}\t{}\t{}\n{}\n{}"
177+ "\n".format(bug[7], bug[0], bug[1].date(), status,
178+ importance, milestone, bug[5], bug[8],
179+ lp_link))
180+ resolved_text = '\n'.join(resolved)
181+ return resolved_text
182+
183+
184+def make_notes(lp, milestone, project, revision):
185+ """Return formatted release notes."""
186+
187+ lp_project = lp.projects[project]
188+ lp_milestone = lp_project.getMilestone(name=milestone)
189+
190+ milestone_link = lp_milestone.web_link
191+ meeting_date = u'[Add the meeting date here.]'
192+ release_date = u'[Add the anticipated release date here.]'
193+ features = u'[Add the features changes here.]'
194+
195+ bugs = get_milestone_bugs(lp_milestone, status=['Fix Committed'])
196+ fixed_bugs = format_bug_text(bugs)
197+ num_fixed_bugs = len(bugs)
198+ all_fixed_bugs = ('https://launchpad.net/{}/+bugs?'
199+ 'field.status%3Alist=FIXCOMMITTED'.format(project))
200+
201+ bugs = get_project_bugs(lp_project, tags=['stable-blocker', 'rc-blocker'])
202+ blocker_tag_bugs = format_bug_text(bugs)
203+ num_blocker_bugs = len(bugs)
204+
205+ bugs = get_project_bugs(lp_project, tags=['cdo-qa-blocker'])
206+ cdo_blocker_bugs = format_bug_text(bugs)
207+ num_cdo_blocker_bugs = len(bugs)
208+
209+ bugs = get_project_bugs(lp_project, importance=['Critical'])
210+ critical_bugs = format_bug_text(bugs)
211+ num_critical_bugs = len(bugs)
212+
213+ stakeholders = (u'<Juju>, <Conjure-up>, <CDO/OIL>, <Docs>, <CPE>,'
214+ '<Openstack>, <Landscape>, <JAAS/GUI>, <LXD>, <MAAS>')
215+ project_ci_link = u'http://qa.jujucharms.com/releases/{}'.format(revision)
216+ project_ci_results = u'[Fill in basic information from project CI]'
217+ cdo_qa_link = u'http://people.canonical.com/~jog/cdo-qa-test-report/'
218+ cdo_qa_results = u'[Fill in compatibility matrix information from CDO QA]'
219+ docs = u'[Add the link to the documentation here.]'
220+ release_notes = u'[Add the link to release notes here.]'
221+
222+ text = TEMPLATE.format(
223+ project=project,
224+ milestone=milestone,
225+ milestone_link=milestone_link,
226+ stakeholders=stakeholders,
227+ meeting_date=meeting_date,
228+ release_date=release_date,
229+ project_ci_link=project_ci_link,
230+ project_ci_results=project_ci_results,
231+ cdo_qa_link=cdo_qa_link,
232+ cdo_qa_results=cdo_qa_results,
233+ features=features,
234+ num_fixed_bugs=num_fixed_bugs,
235+ fixed_bugs=fixed_bugs,
236+ all_fixed_bugs=all_fixed_bugs,
237+ num_cdo_blocker_bugs=num_cdo_blocker_bugs,
238+ cdo_blocker_bugs=cdo_blocker_bugs,
239+ num_blocker_bugs=num_blocker_bugs,
240+ blocker_tag_bugs=blocker_tag_bugs,
241+ num_critical_bugs=num_critical_bugs,
242+ critical_bugs=critical_bugs,
243+ docs=docs,
244+ release_notes=release_notes)
245+
246+ # Normalise the whitespace between sections. The text can have
247+ # extra whitespae when blank sections are interpolated.
248+ text = text.replace('\n\n\n\n', '\n\n\n')
249+ return text
250+
251+
252+def save_notes(text, file_name):
253+ """Save the notes to the named file or print to stdout."""
254+ if file_name is None:
255+ sys.stdout.write(text)
256+ else:
257+ with open(file_name, 'w') as rn:
258+ rn.write(text)
259+
260+
261+def parse_args(args=None):
262+ parser = ArgumentParser(description='Create release checklist '
263+ 'for a milestone')
264+ parser.add_argument(
265+ '--file-name', help='the name of file to write.', default=None)
266+ parser.add_argument('project', help='the launchpad project to examine.')
267+ parser.add_argument('milestone', help='the milestone to examine.')
268+ parser.add_argument('revisionbuild', help='the CI revision build to '
269+ 'examine.')
270+ parser.add_argument('-c', '--credentials', default=None,
271+ help='Launchpad credentials file, '
272+ 'or use "SYSTEM" to find one.')
273+ return parser.parse_args(args)
274+
275+
276+def get_lp(credentials_file):
277+ if credentials_file == 'SYSTEM':
278+ from jujureports.jobs.utils import get_credentials_file
279+ credentials_file = get_credentials_file()
280+ lp = Launchpad.login_with(
281+ 'release-checklist', service_root='https://api.launchpad.net',
282+ version='devel', credentials_file=credentials_file)
283+ return lp
284+
285+
286+def main(argv):
287+ args = parse_args(argv[1:])
288+ lp = get_lp(args.credentials)
289+ print('Generating notes for {} based on milestone {}, revision {}'.format(
290+ args.project, args.milestone, args.revisionbuild))
291+ text = make_notes(lp, args.milestone, args.project, args.revisionbuild)
292+ save_notes(text, args.file_name)
293+ return 0
294+
295+
296+if __name__ == '__main__':
297+ sys.exit(main(sys.argv))

Subscribers

People subscribed via source and target branches