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