blob: 6aab7f3e446a5b53587a5939015887558e03bea6 [file] [log] [blame]
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Enforces luci-milo.cfg consistency.
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details on the presubmit API built into depot_tools.
"""
PRESUBMIT_VERSION = '2.0.0'
USE_PYTHON3 = True
_IGNORE_FREEZE_FOOTER = 'Ignore-Freeze'
# The time module's handling of timezones is abysmal, so the boundaries are
# precomputed in UNIX time
_FREEZE_START = 1639641600 # 2021/12/16 00:00 -0800
_FREEZE_END = 1641196800 # 2022/01/03 00:00 -0800
def CheckFreeze(input_api, output_api):
if _FREEZE_START <= input_api.time.time() < _FREEZE_END:
footers = input_api.change.GitFootersFromDescription()
if _IGNORE_FREEZE_FOOTER not in footers:
def convert(t):
ts = input_api.time.localtime(t)
return input_api.time.strftime('%Y/%m/%d %H:%M %z', ts)
return [
output_api.PresubmitError(
'There is a prod freeze in effect from {} until {},'
' files in //infra/config cannot be modified'.format(
convert(_FREEZE_START), convert(_FREEZE_END)))
]
return []
def CheckTests(input_api, output_api):
glob = input_api.os_path.join(input_api.PresubmitLocalPath(), '*_test.py')
tests = input_api.canned_checks.GetUnitTests(input_api,
output_api,
input_api.glob(glob),
run_on_python2=False,
run_on_python3=True,
skip_shebang_check=True)
return input_api.RunTests(tests)
def CheckLintLuciMilo(input_api, output_api):
if ('infra/config/generated/luci/luci-milo.cfg' in input_api.LocalPaths()
or 'infra/config/lint-luci-milo.py' in input_api.LocalPaths()):
return input_api.RunTests([
input_api.Command(
name='lint-luci-milo',
cmd=[input_api.python_executable, 'lint-luci-milo.py'],
kwargs={},
message=output_api.PresubmitError),
])
return []
def CheckTestingBuildbot(input_api, output_api):
if ('infra/config/generated/luci/luci-milo.cfg' in input_api.LocalPaths() or
'infra/config/generated/luci/luci-milo-dev.cfg' in input_api.LocalPaths()
):
return input_api.RunTests([
input_api.Command(
name='testing/buildbot config checks',
cmd=[input_api.python_executable, input_api.os_path.join(
'..', '..', 'testing', 'buildbot',
'generate_buildbot_json.py',),
'--check'],
kwargs={},
message=output_api.PresubmitError),
])
return []
def CheckLucicfgGenOutputMain(input_api, output_api):
return input_api.RunTests(input_api.canned_checks.CheckLucicfgGenOutput(
input_api, output_api, 'main.star'))
def CheckLucicfgGenOutputDev(input_api, output_api):
return input_api.RunTests(input_api.canned_checks.CheckLucicfgGenOutput(
input_api, output_api, 'dev.star'))
def CheckChangedLUCIConfigs(input_api, output_api):
return input_api.canned_checks.CheckChangedLUCIConfigs(
input_api, output_api)
# Footer indicating a CL that is trying to address an outage by some mechanism
# other than those in infra/config/outages
_OUTAGE_ACTION_FOOTER = 'Infra-Config-Outage-Action'
# Footer acknowledging that an outages configuration is in effect when making an
# unrelated change
_IGNORE_OUTAGE_FOOTER = 'Infra-Config-Ignore-Outage'
def CheckOutagesConfigOnCommit(input_api, output_api):
outages_pyl = input_api.os_path.join(
input_api.PresubmitLocalPath(), 'generated/outages.pyl')
with open(outages_pyl) as f:
outages_config = input_api.ast.literal_eval(f.read())
if not outages_config:
footers = input_api.change.GitFootersFromDescription()
return [
output_api.PresubmitError(
'There is no outages configuration in effect, '
'please remove the {} footer from your CL description.'
.format(footer))
for footer in (_OUTAGE_ACTION_FOOTER, _IGNORE_OUTAGE_FOOTER)
if footer in footers
]
# Any of the config files under infra/config/outages
outages_config_files = set()
# Any of the config files under infra/config/generated
generated_config_files = set()
# Any config files that are not under infra/config/outages or
# infra/config/generated
config_files = set()
for p in input_api.LocalPaths():
if p in ('README.md', 'OWNERS'):
continue
if p.startswith('infra/config/outages/'):
outages_config_files.add(p)
continue
if p.startswith('infra/config/generated/'):
generated_config_files.add(p)
continue
config_files.add(p)
# If the only changes to non-generated config fies were the outages files,
# assume the change was addressing an outage and that no additional mechanism
# needs to be added
if outages_config_files and not config_files:
# REVIEWER: Should we prevent the footers from being here in this case?
return []
# If any non-generated, non-outages files were modified or if the generated
# config files were modified without any config files being modified (lucicfg
# change, etc.) then make sure the user knows that when the outages
# configuration is disabled, the generated configuration may change
if config_files or generated_config_files:
footers = input_api.change.GitFootersFromDescription()
has_action_footer = _OUTAGE_ACTION_FOOTER in footers
has_ignore_footer = _IGNORE_OUTAGE_FOOTER in footers
if has_action_footer and has_ignore_footer:
return [
output_api.PresubmitError(
'Only one of {} or {} should be present in your CL description'
.format(_OUTAGE_ACTION_FOOTER, _IGNORE_OUTAGE_FOOTER)),
]
if not has_action_footer and not has_ignore_footer:
outages_config_lines = ['{}: {}'.format(k, v)
for k, v in sorted(outages_config.items())]
return [
output_api.PresubmitError('\n'.join([
'The following outages configuration is in effect:\n {}'.format(
'\n '.join(outages_config_lines)),
('The effect of your change may not be visible '
'in the generated configuration.'),
('If your change is addressing the outage, '
'please add the footer {} with a link for the outage.'
).format(_OUTAGE_ACTION_FOOTER),
('If your change is not addressing the outage '
'but you still wish to land it, please add the footer '
'{} with a reason.').format(_IGNORE_OUTAGE_FOOTER),
('For more information on outages configuration, '
'see https://chromium.googlesource.com/chromium/src/+/HEAD/infra/config/outages'
),
])),
]
return []