98 lines
3.5 KiB
Python
98 lines
3.5 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 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
|