# 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.python3_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.python3_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 []