# 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 json import urllib.error import urllib.request from mozbuild.vendor.moz_yaml import load_moz_yaml from mozlint import result from mozlint.pathutils import expand_exclusions class UpdatebotValidator: def __init__(self): self._bmo_cache = {} def lint_file(self, path, **kwargs): if not kwargs.get("testing", False): if not path.endswith("moz.yaml"): return None if "test/files/updatebot" in path: return None try: yaml = load_moz_yaml(path) if "vendoring" in yaml and yaml["vendoring"].get("flavor", None) == "rust": yaml_revision = yaml["origin"]["revision"] with open("Cargo.lock") as f: for line in f: if yaml_revision in line: break else: return f"Revision {yaml_revision} specified in {path} wasn't found in Cargo.lock" if "bugzilla" in yaml: product = yaml["bugzilla"].get("product") component = yaml["bugzilla"].get("component") if product and component: if not self._is_valid_bmo_category(product, component): return ( f"Invalid Bugzilla product/component in {path}: '{product} / {component}'.\n" f"If {path} was recently modified, it's likely the Bugzilla information was not correctly supplied.\n" f"If it has not been recently modified, it's likely the Bugzilla component was recently renamed and it must be updated." ) return None except Exception as e: return f"Could not load {path} according to schema in moz_yaml.py: {e}" def _is_valid_bmo_category(self, product, component): cache_key = (product, component) if cache_key in self._bmo_cache: return self._bmo_cache[cache_key] url = ( f"https://bugzilla.mozilla.org/rest/component?" f"product={urllib.parse.quote(product)}&component={urllib.parse.quote(component)}" ) try: with urllib.request.urlopen(url, timeout=5) as response: if response.status == 200: data = json.load(response) valid = "error" not in data self._bmo_cache[cache_key] = valid return valid except urllib.error.HTTPError as e: # Most likely a 404-style result with a useful JSON error body try: data = json.load(e) if data.get("error") == 1: self._bmo_cache[cache_key] = False return False except Exception: return True # Be lenient except Exception: return True # Be lenient return True # Be lenient def lint(paths, config, **lintargs): if not isinstance(paths, list): paths = [paths] errors = [] files = list(expand_exclusions(paths, config, lintargs["root"])) validator = UpdatebotValidator() for f in files: message = validator.lint_file(f, **lintargs) if message: errors.append(result.from_config(config, path=f, message=message)) return errors