Skip to content

Commit a4520f8

Browse files
committed
mask: support auto filing of last-rite bug & PMASKED bugs
Signed-off-by: Arthur Zamarin <arthurzam@gentoo.org>
1 parent b317059 commit a4520f8

2 files changed

Lines changed: 119 additions & 36 deletions

File tree

data/share/bash-completion/completions/pkgdev

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,12 @@ _pkgdev() {
121121
-r --rites
122122
-b --bug
123123
--email
124+
--api-key
125+
--file-bug
124126
"
125127

126128
case "${prev}" in
127-
-[rb] | --rites | --bugs)
129+
-[rb] | --rites | --bugs | --api-key)
128130
COMPREPLY=()
129131
;;
130132
*)

src/pkgdev/scripts/pkgdev_mask.py

Lines changed: 116 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import json
12
import os
23
import re
34
import shlex
45
import subprocess
56
import tempfile
67
import textwrap
8+
import urllib.request as urllib
79
from collections import deque
810
from dataclasses import dataclass
911
from datetime import datetime, timedelta, timezone
@@ -20,13 +22,14 @@
2022
from snakeoil.strings import pluralism
2123

2224
from .. import git
23-
from .argparsers import cwd_repo_argparser, git_repo_argparser
25+
from .argparsers import cwd_repo_argparser, git_repo_argparser, BugzillaApiKey
2426

2527
mask = arghparse.ArgumentParser(
2628
prog="pkgdev mask",
2729
description="mask packages",
2830
parents=(cwd_repo_argparser, git_repo_argparser),
2931
)
32+
BugzillaApiKey.mangle_argparser(mask)
3033
mask.add_argument(
3134
"targets",
3235
metavar="TARGET",
@@ -81,11 +84,26 @@
8184
``x11-misc/xdg-utils`` package.
8285
""",
8386
)
87+
mask_opts.add_argument(
88+
"--file-bug",
89+
action="store_true",
90+
help="file a last-rite bug",
91+
docs="""
92+
Files a last-rite bug for the masked package, which blocks listed
93+
reference bugs. ``PMASKED`` keyword is added all all referenced bugs.
94+
""",
95+
)
8496

8597

8698
@mask.bind_final_check
8799
def _mask_validate(parser, namespace):
88-
atoms = []
100+
atoms = set()
101+
maintainers = set()
102+
103+
if not namespace.rites and namespace.file_bug:
104+
mask.error("bug filing requires last rites")
105+
if namespace.file_bug and not namespace.api_key:
106+
mask.error("bug filing requires a Bugzilla API key")
89107

90108
if namespace.email and not namespace.rites:
91109
mask.error("last rites required for email support")
@@ -96,23 +114,30 @@ def _mask_validate(parser, namespace):
96114
restrict = namespace.repo.path_restrict(x)
97115
pkg = next(namespace.repo.itermatch(restrict))
98116
atom = pkg.versioned_atom
117+
maintainers.update(maintainer.email for maintainer in pkg.maintainers)
99118
else:
100119
try:
101120
atom = atom_cls(x)
102121
except MalformedAtom:
103122
mask.error(f"invalid atom: {x!r}")
104-
if not namespace.repo.match(atom):
123+
if pkgs := namespace.repo.match(atom):
124+
maintainers.update(
125+
maintainer.email for pkg in pkgs for maintainer in pkg.maintainers
126+
)
127+
else:
105128
mask.error(f"no repo matches: {x!r}")
106-
atoms.append(atom)
129+
atoms.add(atom)
107130
else:
108131
restrict = namespace.repo.path_restrict(os.getcwd())
109132
# repo, category, and package level restricts
110133
if len(restrict) != 3:
111134
mask.error("not in a package directory")
112135
pkg = next(namespace.repo.itermatch(restrict))
113-
atoms.append(pkg.unversioned_atom)
136+
atoms.add(pkg.unversioned_atom)
137+
maintainers.update(maintainer.email for maintainer in pkg.maintainers)
114138

115139
namespace.atoms = sorted(atoms)
140+
namespace.maintainers = sorted(maintainers) or ["maintainer-needed@gentoo.org"]
116141

117142

118143
@dataclass(frozen=True)
@@ -208,38 +233,24 @@ def __str__(self):
208233
return "".join(self.header) + "\n\n".join(map(str, self.masks))
209234

210235

211-
def get_comment(bugs, rites: int):
236+
def get_comment():
212237
"""Spawn editor to get mask comment."""
213238
tmp = tempfile.NamedTemporaryFile(mode="w")
214-
summary = []
215-
if rites:
216-
summary.append(f"Removal on {datetime.now(timezone.utc) + timedelta(days=rites):%Y-%m-%d}.")
217-
if bugs:
218-
# Bug(s) #A, #B, #C
219-
bug_list = ", ".join(f"#{b}" for b in bugs)
220-
s = pluralism(bugs)
221-
summary.append(f"Bug{s} {bug_list}.")
222-
if summary := " ".join(summary):
223-
tmp.write(f"\n{summary}")
224239
tmp.write(
225240
textwrap.dedent(
226241
"""
227242
228243
# Please enter the mask message. Lines starting with '#' will be ignored.
229244
#
230-
# - Best last rites (removal) practices -
245+
# If last-rite was requested, it would be added automatically.
231246
#
232-
# Include the following info:
233-
# a) reason for masking
234-
# b) bug # for the removal (and yes you should have one)
235-
# c) date of removal (either the date or "in x days")
247+
# For rules on writing mask messages, see GLEP-84:
248+
# https://glep.gentoo.org/glep-0084.html
236249
#
237250
# Example:
238251
#
239-
# Masked for removal in 30 days. Doesn't work
240-
# with new libfoo. Upstream dead, gtk-1, smells
252+
# Doesn't work with new libfoo. Upstream dead, gtk-1, smells
241253
# funny.
242-
# Bug #987654
243254
"""
244255
)
245256
)
@@ -262,10 +273,71 @@ def get_comment(bugs, rites: int):
262273
comment = "\n".join(comment).strip().splitlines()
263274
if not comment:
264275
mask.error("empty mask comment")
265-
266276
return comment
267277

268278

279+
def message_removal_notice(bugs: list[int], rites: int):
280+
summary = []
281+
if rites:
282+
summary.append(f"Removal on {datetime.now(timezone.utc) + timedelta(days=rites):%Y-%m-%d}.")
283+
if bugs:
284+
# Bug(s) #A, #B, #C
285+
bug_list = ", ".join(f"#{b}" for b in bugs)
286+
s = pluralism(bugs)
287+
summary.append(f"Bug{s} {bug_list}.")
288+
return " ".join(summary)
289+
290+
291+
def file_last_rites_bug(options, message: str) -> int:
292+
summary = f"{', '.join(map(str, options.atoms))}: removal"
293+
if len(summary) > 90 and len(options.atoms) > 1:
294+
summary = f"{options.atoms[0]} and friends: removal"
295+
request_data = dict(
296+
Bugzilla_api_key=options.api_key,
297+
product="Gentoo Linux",
298+
component="Current packages",
299+
version="unspecified",
300+
summary=summary,
301+
description="\n".join([*message, "", "package list:", *map(str, options.atoms)]).strip(),
302+
keywords=["PMASKED"],
303+
assigned_to=options.maintainers[0],
304+
cc=options.maintainers[1:] + ["treecleaner@gentoo.org"],
305+
deadline=(datetime.now(timezone.utc) + timedelta(days=options.rites)).strftime("%Y-%m-%d"),
306+
blocks=list(options.bugs),
307+
)
308+
request = urllib.Request(
309+
url="https://bugs.gentoo.org/rest/bug",
310+
data=json.dumps(request_data).encode("utf-8"),
311+
method="POST",
312+
headers={
313+
"Content-Type": "application/json",
314+
"Accept": "application/json",
315+
},
316+
)
317+
with urllib.urlopen(request, timeout=30) as response:
318+
reply = json.loads(response.read().decode("utf-8"))
319+
return int(reply["id"])
320+
321+
322+
def update_bugs_pmasked(api_key: str, bugs: list[int]):
323+
request_data = dict(
324+
Bugzilla_api_key=api_key,
325+
ids=bugs,
326+
keywords=dict(add=["PMASKED"]),
327+
)
328+
request = urllib.Request(
329+
url=f"https://bugs.gentoo.org/rest/bug/{bugs[0]}",
330+
data=json.dumps(request_data).encode("utf-8"),
331+
method="PUT",
332+
headers={
333+
"Content-Type": "application/json",
334+
"Accept": "application/json",
335+
},
336+
)
337+
with urllib.urlopen(request, timeout=30) as response:
338+
return response.status == 200
339+
340+
269341
def send_last_rites_email(m: Mask, subject_prefix: str):
270342
try:
271343
atoms = ", ".join(map(str, m.atoms))
@@ -298,16 +370,25 @@ def _mask(options, out, err):
298370
p = git.run("config", "user.email", stdout=subprocess.PIPE)
299371
email = p.stdout.strip()
300372

301-
# initial args for Mask obj
302-
mask_args = {
303-
"author": author,
304-
"email": email,
305-
"date": today.strftime("%Y-%m-%d"),
306-
"comment": get_comment(options.bugs, options.rites),
307-
"atoms": options.atoms,
308-
}
309-
310-
m = Mask(**mask_args)
373+
message = get_comment()
374+
if options.file_bug:
375+
if bug_no := file_last_rites_bug(options, message):
376+
out.write(out.fg("green"), f"filed bug https://bugs.gentoo.org/{bug_no}", out.reset)
377+
out.flush()
378+
if not update_bugs_pmasked(options.api_key, options.bugs):
379+
err.write(err.fg("red"), "failed to update referenced bugs", err.reset)
380+
err.flush()
381+
options.bugs.insert(0, bug_no)
382+
if removal := message_removal_notice(options.bugs, options.rites):
383+
message.append(removal)
384+
385+
m = Mask(
386+
author=author,
387+
email=email,
388+
date=today.strftime("%Y-%m-%d"),
389+
comment=message,
390+
atoms=options.atoms,
391+
)
311392
mask_file.add(m)
312393
mask_file.write()
313394

0 commit comments

Comments
 (0)