Files
sousa-gecko/security/mach_commands.py
T

199 lines
6.3 KiB
Python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import getpass
import json
import logging
import os
import re
import subprocess
import urllib.error
import urllib.request
import yaml
from mach.decorators import Command, CommandArgument
from mach.util import UserError
BUGZILLA_API_URL = "https://bugzilla.mozilla.org/rest/bug"
NSS_TRACKER_BUG = 1816499
def get_blocking_bug():
securitydir = os.path.dirname(__file__)
with open(os.path.join(securitydir, "nss", "moz.yaml")) as f:
manifest = yaml.load(f, Loader=yaml.BaseLoader)
if "updatebot" not in manifest:
raise UserError("moz.yaml must have an updatebot section")
updatebot = manifest["updatebot"]
if "tasks" not in manifest["updatebot"]:
raise UserError("updatebot section of moz.yaml must have tasks")
tasks = updatebot["tasks"]
vendoring_task = [
task for task in tasks if "type" in task and task["type"] == "vendoring"
]
if len(vendoring_task) != 1:
raise UserError(
"updatebot section of moz.yaml must have exactly one vendoring task"
)
vendoring_task = vendoring_task[0]
if "blocking" not in vendoring_task:
raise UserError(
"vendoring task of updatebot section of moz.yaml must have a blocking bug"
)
return vendoring_task["blocking"]
def next_nss_version(tag):
"""Convert the NSS version tag to the next dotted version, e.g. 'NSS_3_123_RTM' to '3.124'."""
if not (tag.startswith("NSS_") and tag.endswith("_RTM")):
return None
parts = tag[4:-4].rsplit("_", 1) # ["3", "123"]
if len(parts) != 2:
return None
try:
return f"{parts[0]}.{int(parts[1]) + 1}"
except ValueError:
return None
def next_firefox_version():
securitydir = os.path.dirname(__file__)
version_path = os.path.join(securitydir, "..", "browser", "config", "version.txt")
with open(version_path) as f:
version = f.read().strip()
return str(int(version.split(".")[0]) + 1)
def create_next_nss_uplift_bug(bug, api_key):
data = json.dumps(bug).encode("utf-8")
req = urllib.request.Request(BUGZILLA_API_URL, data=data, method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("X-BUGZILLA-API-KEY", api_key)
try:
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())["id"]
except urllib.error.HTTPError as e:
raise UserError(f"Bugzilla API error {e.code}: {e.read().decode()}") from e
def update_blocking_bug(old_bug_id, new_bug_id):
securitydir = os.path.dirname(__file__)
moz_yaml_path = os.path.join(securitydir, "nss", "moz.yaml")
with open(moz_yaml_path) as f:
content = f.read()
updated = re.sub(
rf"(blocking:\s*){re.escape(str(old_bug_id))}\b",
rf"\g<1>{new_bug_id}",
content,
)
if updated == content:
raise UserError("Could not find blocking bug number in moz.yaml to update")
with open(moz_yaml_path, "w") as f:
f.write(updated)
@Command(
"nss-uplift",
category="devenv",
description="Upgrade to a tagged release of NSS",
)
@CommandArgument(
"tag",
nargs=1,
help="The tagged release or commit to upgrade to.",
)
def nss_uplift(command_context, tag):
tag = tag[0]
result = subprocess.run(
["git", "status", "--porcelain"], capture_output=True, text=True, check=True
)
if result.stdout.strip():
raise UserError(
"Working tree is not clean. Please commit or stash your changes."
)
result = subprocess.run(
["./mach", "vendor", "security/nss/moz.yaml", "--revision", tag], check=True
)
if tag.startswith("NSS_"):
with open("security/nss/TAG-INFO", "w") as f:
f.write(tag)
result = subprocess.run(
["git", "status", "--porcelain"], capture_output=True, text=True, check=True
)
assert result.returncode == 0
if ".def" in result.stdout:
command_context.log(
logging.WARNING,
"nss_uplift",
{},
"Changes in .def. We might have to change security/nss.symbols then manually",
)
blocking_bug = get_blocking_bug()
if "_RTM" in tag:
nss_version = next_nss_version(tag)
if nss_version is None:
raise UserError(f"Could not parse NSS version from tag: {tag}")
fx_version = next_firefox_version()
bug = {
"product": "Core",
"component": "Security: PSM",
"version": "unspecified",
"summary": f"Upgrade Firefox {fx_version} to NSS {nss_version}",
"type": "task",
"priority": "P1",
"severity": "N/A",
"keywords": ["leave-open"],
"blocks": [NSS_TRACKER_BUG],
}
print("\n" + "=" * 60)
print(json.dumps(bug, indent=2))
answer = input("Create this bug? [y/N] ").strip().lower()
print("=" * 60 + "\n")
if answer not in ("y", "yes"):
command_context.log(
logging.WARNING,
"nss_uplift",
{},
"Create a bug for the next release, update the blocking bug for updatebot in security/nss/moz.yaml, and commit your changes",
)
return 0
print(
"Get an API key from https://bugzilla.mozilla.org/userprefs.cgi?tab=apikey"
)
api_key = getpass.getpass("Bugzilla API key: ")
if not api_key:
command_context.log(
logging.WARNING, "nss_uplift", {}, "No API key provided"
)
return 1
new_bug_id = create_next_nss_uplift_bug(bug, api_key)
command_context.log(
logging.INFO,
"nss_uplift",
{"bug_id": new_bug_id},
"Created bug {bug_id}: https://bugzilla.mozilla.org/{bug_id}",
)
update_blocking_bug(blocking_bug, new_bug_id)
result = subprocess.run(
[
"git",
"commit",
"-a",
"-m",
f"Bug {blocking_bug} - upgrade NSS to {tag}. r=#nss-reviewers UPGRADE_NSS_RELEASE",
],
capture_output=True,
text=True,
check=True,
)
return result.returncode