From 532c1171316bd69c3e0140ba43269db61cc9427f Mon Sep 17 00:00:00 2001
From: Chenxiong Qi <cqi@redhat.com>
Date: Jun 06 2018 07:21:02 +0000
Subject: Add new command for creating override in Bodhi


To create a buildroot override, run inside a package repo:

    fedpkg override create --duration 5

It also accepts a specified build:

    fedpkg override create --duration 5 rpkg-1.54-2.fc27

Fixes #92

Signed-off-by: Chenxiong Qi <cqi@redhat.com>

---

diff --git a/fedpkg/__init__.py b/fedpkg/__init__.py
index 683a1a8..7f09097 100644
--- a/fedpkg/__init__.py
+++ b/fedpkg/__init__.py
@@ -15,6 +15,8 @@ import git
 import re
 import platform
 
+from datetime import datetime
+
 from . import cli  # noqa
 from .lookaside import FedoraLookasideCache
 from pyrpkg.utils import cached_property
@@ -234,6 +236,41 @@ class Commands(pyrpkg.Commands):
         cmd.append(self.nvr)
         self._run_command(cmd, shell=True)
 
+    def create_buildroot_override(self, bodhi_config, build, duration,
+                                  notes=''):
+        from bodhi.client.bindings import BodhiClient
+
+        bodhi = BodhiClient(staging=bodhi_config['staging'])
+        result = bodhi.list_overrides(builds=build)
+        if result['total'] == 0:
+            try:
+                self.log.debug(
+                    'Create override in %s: nvr=%s, duration=%s, notes="%s"',
+                    'staging Bodhi' if bodhi_config['staging'] else 'Bodhi',
+                    build, duration, notes)
+                override = bodhi.save_override(
+                    nvr=build, duration=duration, notes=notes)
+            except Exception as e:
+                self.log.error(str(e))
+                raise pyrpkg.rpkgError('Cannot create override.')
+            else:
+                self.log.info('Override is created.')
+                self.log.info('Expiration date: %s',
+                              override['expiration_date'])
+                self.log.info('Notes: %s', override['notes'])
+        else:
+            override = result['overrides'][0]
+            expiration_date = datetime.strptime(override['expiration_date'],
+                                                '%Y-%m-%d %H:%M:%S')
+            if expiration_date < datetime.now():
+                self.log.info(
+                    'Buildroot override for %s exists and is expired. Consider'
+                    ' using command `override extend` to extend duration.',
+                    build)
+            else:
+                self.log.info('Buildroot override for %s already exists and '
+                              'not expired.', build)
+
 
 if __name__ == "__main__":
     from fedpkg.__main__ import main
diff --git a/fedpkg/cli.py b/fedpkg/cli.py
index 2b21719..d55259a 100644
--- a/fedpkg/cli.py
+++ b/fedpkg/cli.py
@@ -40,7 +40,7 @@ def check_bodhi_version():
         dist = pkg_resources.get_distribution('bodhi_client')
     except pkg_resources.DistributionNotFound:
         raise rpkgError('bodhi-client < 2.0 is not supported.')
-    major = int(dist.version.split('.', 1))
+    major = int(dist.version.split('.', 1)[0])
     if major >= 4:
         raise rpkgError(
             'This system has bodhi v{0}, which is unsupported.'.format(major))
@@ -72,6 +72,7 @@ class fedpkgClient(cliClient):
         self.register_request_repo()
         self.register_request_tests_repo()
         self.register_request_branch()
+        self.register_override()
 
     # Target registry goes here
     def register_retire(self):
@@ -229,6 +230,62 @@ and created:
         )
         request_branch_parser.set_defaults(command=self.request_branch)
 
+    def register_override(self):
+        """Register command line parser for subcommand override"""
+
+        def validate_duration(value):
+            try:
+                duration = int(value)
+            except ValueError:
+                raise argparse.ArgumentTypeError('duration must be an integer.')
+            if duration > 0:
+                return duration
+            raise argparse.ArgumentTypeError(
+                'override should have 1 day to exist at least.')
+
+        override_parser = self.subparsers.add_parser(
+            'override',
+            help='Manage buildroot overrides')
+        override_subparser = override_parser.add_subparsers(
+            description='Commands on override')
+
+        create_parser = override_subparser.add_parser(
+            'create',
+            help='Create buildroot override from build',
+            formatter_class=argparse.RawDescriptionHelpFormatter,
+            description='''\
+Create a buildroot override from build guessed from current release branch or
+specified explicitly.
+
+Examples:
+
+Create a buildroot override from build guessed from release branch. Note that,
+command must run inside a package repository.
+
+    fedpkg switch-branch f28
+    fedpkg override create --duration 5
+
+Create for a specified build:
+
+    fedpkg override create --duration 5 package-1.0-1.fc28
+''')
+        create_parser.add_argument(
+            '--duration',
+            type=validate_duration,
+            default=7,
+            help='Number of days the override should exist. If omitted, '
+                 'default to 7 days.')
+        create_parser.add_argument(
+            '--notes',
+            default='No explanation given...',
+            help='Optional notes on why this override is in place.')
+        create_parser.add_argument(
+            'NVR',
+            nargs='?',
+            help='Create override from this build. If omitted, build will be'
+                 ' guessed from current release branch.')
+        create_parser.set_defaults(command=self.create_buildroot_override)
+
     # Target functions go here
     def retire(self):
         # Skip if package is already retired...
@@ -254,20 +311,21 @@ and created:
         log.append('#')
         return lines[0], "\n".join(log)
 
-    def update(self):
-        check_bodhi_version()
-
+    def _get_bodhi_config(self):
         try:
             section = '%s.bodhi' % self.name
-            bodhi_config = {
+            return {
                 'staging': self.config.getboolean(section, 'staging'),
-                }
+            }
         except (ValueError, NoOptionError, NoSectionError) as e:
             self.log.error(str(e))
             raise rpkgError('Could not get bodhi options. It seems configuration is changed. '
                             'Please try to reinstall %s or consult developers to see what '
                             'is wrong with it.' % self.name)
 
+    def update(self):
+        check_bodhi_version()
+        bodhi_config = self._get_bodhi_config()
         template = """\
 [ %(nvr)s ]
 
@@ -630,3 +688,13 @@ suggest_reboot=False
                     name=name,
                     config=config,
                 )
+
+    def create_buildroot_override(self):
+        """Create a buildroot override in Bodhi"""
+        check_bodhi_version()
+        bodhi_config = self._get_bodhi_config()
+        self.cmd.create_buildroot_override(
+            bodhi_config,
+            build=self.args.NVR or self.cmd.nvr,
+            duration=self.args.duration,
+            notes=self.args.notes)
diff --git a/test/test_cli.py b/test/test_cli.py
index 3de4bd5..a71c6ba 100644
--- a/test/test_cli.py
+++ b/test/test_cli.py
@@ -15,7 +15,16 @@ import os
 import sys
 import json
 import pkg_resources
-import unittest
+
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
+
+try:
+    import bodhi
+except ImportError:
+    bodhi = None
 
 from datetime import datetime, timedelta
 from tempfile import mkdtemp
@@ -1122,3 +1131,126 @@ class TestCheckBodhiVersion(unittest.TestCase):
         six.assertRaisesRegex(
             self, rpkgError, r'bodhi-client < 2\.0 is not supported\.',
             check_bodhi_version)
+
+
+@unittest.skipUnless(bodhi, 'Skip if no supported bodhi-client is available')
+class TestBodhiOverride(CliTestCase):
+    """Test command override"""
+
+    @patch('fedpkg.cli.check_bodhi_version')
+    @patch('bodhi.client.bindings.BodhiClient')
+    def test_create_for_given_build(self, BodhiClient, check_bodhi_version):
+        bodhi_client = BodhiClient.return_value
+        bodhi_client.list_overrides.return_value = {'total': 0}
+        expiration_date = datetime.now() + timedelta(days=7)
+        new_override = {
+            'expiration_date': expiration_date.strftime('%Y-%m-%d %H:%M:%S'),
+            'notes': 'build for fedpkg'
+        }
+        bodhi_client.save_override.return_value = new_override
+
+        cli_cmd = [
+            'fedpkg', '--path', self.cloned_repo_path,
+            'override', 'create',
+            '--duration', '7', '--notes', 'build for fedpkg',
+            'rpkg-1.54-1.fc28'
+        ]
+
+        with patch('sys.argv', new=cli_cmd):
+            cli = self.new_cli()
+            with patch.object(cli.cmd, 'log') as log:
+                cli.create_buildroot_override()
+
+                log.info.assert_any_call('Expiration date: %s',
+                                         new_override['expiration_date'])
+                log.info.assert_any_call('Notes: %s', new_override['notes'])
+
+        bodhi_client.save_override.assert_called_once_with(
+            nvr='rpkg-1.54-1.fc28',
+            duration=7,
+            notes='build for fedpkg')
+
+    @patch('fedpkg.cli.check_bodhi_version')
+    @patch('bodhi.client.bindings.BodhiClient')
+    @patch('fedpkg.Commands.nvr', new_callable=PropertyMock)
+    def test_create_from_current_branch(
+            self, nvr, BodhiClient, check_bodhi_version):
+        nvr.return_value = 'rpkg-1.54-2.fc28'
+        bodhi_client = BodhiClient.return_value
+        bodhi_client.list_overrides.return_value = {'total': 0}
+
+        cli_cmd = [
+            'fedpkg', '--path', self.cloned_repo_path,
+            'override', 'create',
+            '--duration', '7', '--notes', 'build for fedpkg',
+        ]
+
+        with patch('sys.argv', new=cli_cmd):
+            cli = self.new_cli()
+            cli.create_buildroot_override()
+
+        bodhi_client.save_override.assert_called_once_with(
+            nvr='rpkg-1.54-2.fc28',
+            duration=7,
+            notes='build for fedpkg')
+
+    @patch('fedpkg.cli.check_bodhi_version')
+    @patch('bodhi.client.bindings.BodhiClient')
+    @patch('fedpkg.Commands.nvr', new_callable=PropertyMock)
+    def test_override_already_exists_but_expired(
+            self, nvr, BodhiClient, check_bodhi_version):
+        nvr.return_value = 'rpkg-1.54-2.fc28'
+        today = datetime.today()
+        fake_expiration_date = today - timedelta(days=10)
+        BodhiClient.return_value.list_overrides.return_value = {
+            'total': 1,
+            'overrides': [{
+                'expiration_date': fake_expiration_date.strftime(
+                    '%Y-%m-%d %H:%M:%S')
+            }]
+        }
+
+        cli_cmd = [
+            'fedpkg', '--path', self.cloned_repo_path,
+            'override', 'create',
+            '--duration', '7', '--notes', 'build for fedpkg',
+        ]
+
+        with patch('sys.argv', new=cli_cmd):
+            cli = self.new_cli()
+            with patch.object(cli.cmd, 'log') as log:
+                cli.create_buildroot_override()
+                log.info.assert_any_call(
+                    'Buildroot override for %s exists and is expired. Consider'
+                    ' using command `override extend` to extend duration.',
+                    'rpkg-1.54-2.fc28')
+
+    @patch('fedpkg.cli.check_bodhi_version')
+    @patch('bodhi.client.bindings.BodhiClient')
+    @patch('fedpkg.Commands.nvr', new_callable=PropertyMock)
+    def test_override_already_exists_but_not_expired(
+            self, nvr, BodhiClient, check_bodhi_version):
+        nvr.return_value = 'rpkg-1.54-2.fc28'
+        today = datetime.today()
+        fake_expiration_date = today + timedelta(days=10)
+        BodhiClient.return_value.list_overrides.return_value = {
+            'total': 1,
+            'overrides': [{
+                'expiration_date': fake_expiration_date.strftime(
+                    '%Y-%m-%d %H:%M:%S')
+            }]
+        }
+
+        cli_cmd = [
+            'fedpkg', '--path', self.cloned_repo_path,
+            'override', 'create',
+            '--duration', '7', '--notes', 'build for fedpkg',
+        ]
+
+        with patch('sys.argv', new=cli_cmd):
+            cli = self.new_cli()
+            with patch.object(cli.cmd, 'log') as log:
+                cli.create_buildroot_override()
+                log.info.assert_any_call(
+                    'Buildroot override for %s already exists and not '
+                    'expired.', 'rpkg-1.54-2.fc28')
diff --git a/tox.ini b/tox.ini
index fdb9934..a0e1b30 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,6 +2,7 @@
 envlist = py26,py27,py36,flake8
 
 [testenv]
+sitepackages=True
 deps =
     -r{toxinidir}/requirements.txt
     -r{toxinidir}/tests-requirements.txt

