blob: 7e1b83b7d6cea3d0f33e8736be9c8dd460e861a9 [file] [log] [blame]
# Copyright 2020 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
import errno
import re
from datetime import datetime
from io import open
from google.protobuf import text_format as textpb
# The relative path to the file where all warnings are defined under recipe
# path (where the "recipes" and/or "recipe_modules" directories sit)
RECIPE_WARNING_DEFINITIONS_REL = 'recipe.warnings'
def parse_warning_definitions(file_path):
"""Parse the warning definition file at the given absolute path. The file
content is expected to be in text proto format of warning.DefinitionCollection
proto message. Duplicate warning names will be raised. Each warning definition
will be validated. The conditions are documented in warning.proto.
Args:
* file_path (str) - Absolute path to warning definition file
Returns a dict of warning name to warning.Definition proto message instance
"""
raw_text = ''
try:
with open(file_path, encoding='utf-8') as f:
raw_text = f.read()
except IOError as ex:
if ex.errno == errno.ENOENT:
# No warning defined
return {}
raise ex
from PB.recipe_engine.warning import DefinitionCollection
definition_collection = textpb.Parse(raw_text, DefinitionCollection())
definitions = list(definition_collection.warning)
_populate_bug_issue_fields(definitions,
definition_collection.monorail_bug_default,
definition_collection.google_issue_default)
ret = {}
for definition in definitions:
if definition.name in ret:
raise ValueError(
'Found warning definitions with duplicate name: %s' % definition.name)
_validate(definition)
ret[definition.name] = definition
return ret
def _populate_bug_issue_fields(definitions, monorail_bug_default,
google_issue_default):
"""If default field value has been declared for bugs/issues, run through all
bugs/issues declared in all warning definitions and assign default value to
fields which are unset.
Args:
* definitions (list of warning.Definition)
* monorail_bug_default (warning.MonorailBugDefault): contains the default
value for some fields (namely, host and project) in warning.MonorailBug
message.
"""
for definition in definitions:
for bug in definition.monorail_bug:
bug.host = bug.host or monorail_bug_default.host
bug.project = bug.project or monorail_bug_default.project
for iss in definition.google_issue:
iss.host = iss.host or google_issue_default.host
def _validate(definition):
"""Ensure the given warning definition is valid. ValueError will be
raised otherwise. All conditions are documented in warning.proto.
Args:
* definition (warning.Definition proto)
"""
if not re.match(r"^[A-Z][A-Z0-9]*(\_[A-Z0-9]+)*$", definition.name):
raise ValueError(
'Expect warning name to be all CAPS or num snake case. Actual: %s' % (
definition.name))
if definition.deadline:
try:
datetime.strptime(definition.deadline, '%Y-%m-%d')
except ValueError:
raise ValueError(
'The deadline should be in YYYY-MM-DD format. Actual: %s' % (
definition.deadline))
for bug in definition.monorail_bug:
err_msg_template = 'Field: %s is required; Got empty value'
_require_non_zero_value(bug.host, err_msg_template % 'host')
_require_non_zero_value(bug.project, err_msg_template % 'project')
_require_non_zero_value(bug.id, err_msg_template % 'id')
for iss in definition.google_issue:
err_msg_template = 'Field: %s is required; Got empty value'
_require_non_zero_value(iss.host, err_msg_template % 'host')
_require_non_zero_value(iss.id, err_msg_template % 'id')
def _require_non_zero_value(value, message):
"""Raise ValueError with message if the supplied value is a zero value
"""
if not value:
raise ValueError(message)