chromium/infra/config/PRESUBMIT.py

# Copyright 2018 The Chromium Authors
# 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'


def CheckFreeze(input_api, output_api):
  return input_api.canned_checks.CheckInfraFreeze(input_api, output_api)


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))
  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)


def CheckPylFilesSynced(input_api, output_api):
  return input_api.RunTests([
      input_api.Command(
          name='check-pyl-files-synced',
          cmd=[
              input_api.python3_executable,
              'scripts/sync-pyl-files.py',
              '--check',
          ],
          kwargs={},
          message=output_api.PresubmitError,
      ),
  ])


# 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, encoding='utf-8') 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 []