Repository: fedora-infra/bodhi

Issue:
Give bodhi-push a -y flag so that it can be run in cron
We should give ```bodhi-push``` a -y flag that answers yes to the various questions so that it can be run in cron.
Give bodhi-push a -y flag so that it can be run in cron
We should give ```bodhi-push``` a -y flag that answers yes to the various questions so that it can be run in cron.


Discussion:

Resolving patch:
diff --git a/bodhi/server/push.py b/bodhi/server/push.py
--- a/bodhi/server/push.py
+++ b/bodhi/server/push.py
@@ -61,8 +61,10 @@ def update_sig_status(update):
 @click.option('--resume', help='Resume one or more previously failed pushes',
               is_flag=True, default=False)
 @click.option('--username', prompt=True)
+@click.option('--yes', '-y', is_flag=True, default=False,
+              help='Answers yes to the various questions')
 @click.version_option(message='%(version)s')
-def push(username, cert_prefix, **kwargs):
+def push(username, cert_prefix, yes, **kwargs):
     """Push builds out to the repositories."""
     resume = kwargs.pop('resume')
     resume_all = False
@@ -72,8 +74,14 @@ def push(username, cert_prefix, **kwargs):
     composes = []
     with db_factory() as session:
         if not resume and session.query(Compose).count():
-            click.confirm('Existing composes detected: {}. Do you wish to resume them all?'.format(
-                ', '.join([str(c) for c in session.query(Compose).all()])), abort=True)
+            if yes:
+                click.echo('Existing composes detected: {}. Resuming all.'.format(
+                    ', '.join([str(c) for c in session.query(Compose).all()])))
+            else:
+                click.confirm(
+                    'Existing composes detected: {}. Do you wish to resume them all?'.format(
+                        ', '.join([str(c) for c in session.query(Compose).all()])),
+                    abort=True)
             resume = True
             resume_all = True
 
@@ -90,8 +98,11 @@ def push(username, cert_prefix, **kwargs):
                     session.delete(compose)
                     continue
 
-                if not resume_all and not click.confirm('Resume {}?'.format(compose)):
-                    continue
+                if not resume_all:
+                    if yes:
+                        click.echo('Resuming {}.'.format(compose))
+                    elif not click.confirm('Resume {}?'.format(compose)):
+                        continue
 
                 # Reset the Compose's state and error message.
                 compose.state = ComposeState.requested
@@ -148,8 +159,12 @@ def push(username, cert_prefix, **kwargs):
                 click.echo(update.title)
 
         if composes:
-            click.confirm('\n\nPush these {:d} updates?'.format(
-                sum([len(c.updates) for c in composes])), abort=True)
+            if yes:
+                click.echo('\n\nPushing {:d} updates.'.format(
+                    sum([len(c.updates) for c in composes])))
+            else:
+                click.confirm('\n\nPush these {:d} updates?'.format(
+                    sum([len(c.updates) for c in composes])), abort=True)
             click.echo('\nLocking updates...')
         else:
             click.echo('\nThere are no updates to push.')


Test patch:
diff --git a/bodhi/tests/server/test_push.py b/bodhi/tests/server/test_push.py
--- a/bodhi/tests/server/test_push.py
+++ b/bodhi/tests/server/test_push.py
@@ -212,6 +212,22 @@ def test_unknown_release(self):
 Sending masher.start fedmsg
 """
 
+TEST_YES_FLAG_EXPECTED_OUTPUT = """
+
+===== <Compose: F17 testing> =====
+
+python-nose-1.3.7-11.fc17
+python-paste-deploy-1.5.2-8.fc17
+bodhi-2.0-1.fc17
+
+
+Pushing 3 updates.
+
+Locking updates...
+
+Sending masher.start fedmsg
+"""
+
 TEST_LOCKED_UPDATES_EXPECTED_OUTPUT = """Existing composes detected: <Compose: F17 testing>. Do you wish to resume them all? [y/N]: y
 
 
@@ -227,6 +243,21 @@ def test_unknown_release(self):
 Sending masher.start fedmsg
 """
 
+TEST_LOCKED_UPDATES_YES_FLAG_EXPECTED_OUTPUT = """Existing composes detected: <Compose: F17 testing>. Resuming all.
+
+
+===== <Compose: F17 testing> =====
+
+ejabberd-16.09-4.fc17
+
+
+Pushing 1 updates.
+
+Locking updates...
+
+Sending masher.start fedmsg
+"""
+
 TEST_RELEASES_FLAG_EXPECTED_OUTPUT = """
 
 ===== <Compose: F25 testing> =====
@@ -276,6 +307,21 @@ def test_unknown_release(self):
 Sending masher.start fedmsg
 """
 
+TEST_RESUME_AND_YES_FLAGS_EXPECTED_OUTPUT = """Resuming <Compose: F17 testing>.
+
+
+===== <Compose: F17 testing> =====
+
+ejabberd-16.09-4.fc17
+
+
+Pushing 1 updates.
+
+Locking updates...
+
+Sending masher.start fedmsg
+"""
+
 TEST_RESUME_EMPTY_COMPOSE = """Resume <Compose: F17 testing>? [y/N]: y
 <Compose: F17 stable> has no updates. It is being removed.
 
@@ -426,6 +472,43 @@ def test_cert_prefix_flag(self, publish, init):
                 'resume': False, 'agent': 'bowlofeggs', 'api_version': 2},
             force=True)
 
+    @mock.patch('bodhi.server.push.bodhi.server.notifications.init')
+    @mock.patch('bodhi.server.push.bodhi.server.notifications.publish')
+    def test_yes_flag(self, publish, init):
+        """
+        Test correct operation when the --yes flag is used.
+        """
+        cli = CliRunner()
+        self.db.commit()
+
+        with mock.patch('bodhi.server.push.transactional_session_maker',
+                        return_value=base.TransactionalSessionMaker(self.Session)):
+            result = cli.invoke(
+                push.push, ['--username', 'bowlofeggs', '--yes'])
+
+        self.assertEqual(result.exit_code, 0)
+        self.assertEqual(result.output, TEST_YES_FLAG_EXPECTED_OUTPUT)
+        init.assert_called_once_with(active=True, cert_prefix='shell')
+        publish.assert_called_once_with(
+            topic='masher.start',
+            msg={
+                'composes': [{'security': False, 'release_id': 1, 'request': u'testing',
+                              'content_type': u'rpm'}],
+                'resume': False, 'agent': 'bowlofeggs', 'api_version': 2},
+            force=True)
+        bodhi = self.db.query(models.Update).filter_by(
+            title=u'bodhi-2.0-1.fc17').one()
+        python_nose = self.db.query(models.Update).filter_by(
+            title=u'python-nose-1.3.7-11.fc17').one()
+        python_paste_deploy = self.db.query(models.Update).filter_by(
+            title=u'python-paste-deploy-1.5.2-8.fc17').one()
+        for u in [bodhi, python_nose, python_paste_deploy]:
+            self.assertTrue(u.locked)
+            self.assertTrue(u.date_locked <= datetime.utcnow())
+            self.assertEqual(u.compose.release.id, python_paste_deploy.release.id)
+            self.assertEqual(u.compose.request, models.UpdateRequest.testing)
+            self.assertEqual(u.compose.content_type, models.ContentType.rpm)
+
     @mock.patch('bodhi.server.push.bodhi.server.notifications.init')
     @mock.patch('bodhi.server.push.bodhi.server.notifications.publish')
     def test_locked_updates(self, publish, mock_init):
@@ -471,6 +554,51 @@ def test_locked_updates(self, publish, mock_init):
             self.assertFalse(u.locked)
             self.assertIsNone(u.date_locked)
 
+    @mock.patch('bodhi.server.push.bodhi.server.notifications.init')
+    @mock.patch('bodhi.server.push.bodhi.server.notifications.publish')
+    def test_locked_updates_yes_flag(self, publish, mock_init):
+        """
+        Test correct operation when there are some locked updates and --yes flag is given.
+        """
+        cli = CliRunner()
+        # Let's mark ejabberd as locked and already in a push. bodhi-push should resume this
+        # compose.
+        ejabberd = self.create_update([u'ejabberd-16.09-4.fc17'])
+        ejabberd.builds[0].signed = True
+        ejabberd.locked = True
+        compose = models.Compose(
+            release=ejabberd.release, request=ejabberd.request, state=models.ComposeState.failed,
+            error_message=u'y r u so mean nfs')
+        self.db.add(compose)
+        self.db.commit()
+
+        with mock.patch('bodhi.server.push.transactional_session_maker',
+                        return_value=base.TransactionalSessionMaker(self.Session)):
+            result = cli.invoke(push.push, ['--username', 'bowlofeggs', '--yes'])
+
+        self.assertEqual(result.exit_code, 0)
+        mock_init.assert_called_once_with(active=True, cert_prefix=u'shell')
+        self.assertEqual(result.output, TEST_LOCKED_UPDATES_YES_FLAG_EXPECTED_OUTPUT)
+        publish.assert_called_once_with(
+            topic='masher.start',
+            msg={'composes': [ejabberd.compose.__json__(composer=True)],
+                 'resume': True, 'agent': 'bowlofeggs', 'api_version': 2},
+            force=True)
+        ejabberd = self.db.query(models.Update).filter_by(title=u'ejabberd-16.09-4.fc17').one()
+        self.assertTrue(ejabberd.locked)
+        self.assertTrue(ejabberd.date_locked <= datetime.utcnow())
+        self.assertEqual(ejabberd.compose.release, ejabberd.release)
+        self.assertEqual(ejabberd.compose.request, ejabberd.request)
+        self.assertEqual(ejabberd.compose.state, models.ComposeState.requested)
+        self.assertEqual(ejabberd.compose.error_message, '')
+        python_nose = self.db.query(models.Update).filter_by(
+            title=u'python-nose-1.3.7-11.fc17').one()
+        python_paste_deploy = self.db.query(models.Update).filter_by(
+            title=u'python-paste-deploy-1.5.2-8.fc17').one()
+        for u in [python_nose, python_paste_deploy]:
+            self.assertFalse(u.locked)
+            self.assertIsNone(u.date_locked)
+
     @mock.patch('bodhi.server.push.bodhi.server.notifications.publish')
     def test_no_updates_to_push(self, publish):
         """
@@ -681,6 +809,50 @@ def test_resume_flag(self, publish, mock_init):
             self.assertIsNone(u.date_locked)
             self.assertIsNone(u.compose)
 
+    @mock.patch('bodhi.server.push.bodhi.server.notifications.init')
+    @mock.patch('bodhi.server.push.bodhi.server.notifications.publish')
+    def test_resume_and_yes_flags(self, publish, mock_init):
+        """
+        Test correct operation when the --resume flag and --yes flag are given.
+        """
+        cli = CliRunner()
+        # Let's mark ejabberd as locked and already in a push. Since we are resuming, it should be
+        # the only package that gets included.
+        ejabberd = self.create_update([u'ejabberd-16.09-4.fc17'])
+        ejabberd.builds[0].signed = True
+        ejabberd.locked = True
+        compose = models.Compose(release=ejabberd.release, request=ejabberd.request)
+        self.db.add(compose)
+        self.db.commit()
+
+        with mock.patch('bodhi.server.push.transactional_session_maker',
+                        return_value=base.TransactionalSessionMaker(self.Session)):
+            result = cli.invoke(push.push, ['--username', 'bowlofeggs', '--resume', '--yes'])
+
+        self.assertEqual(result.exit_code, 0)
+        mock_init.assert_called_once_with(active=True, cert_prefix=u'shell')
+        self.assertEqual(result.output, TEST_RESUME_AND_YES_FLAGS_EXPECTED_OUTPUT)
+        ejabberd = self.db.query(models.Update).filter_by(title=u'ejabberd-16.09-4.fc17').one()
+        python_nose = self.db.query(models.Update).filter_by(
+            title=u'python-nose-1.3.7-11.fc17').one()
+        python_paste_deploy = self.db.query(models.Update).filter_by(
+            title=u'python-paste-deploy-1.5.2-8.fc17').one()
+        publish.assert_called_once_with(
+            topic='masher.start',
+            msg={'composes': [ejabberd.compose.__json__(composer=True)],
+                 'resume': True, 'agent': 'bowlofeggs', 'api_version': 2},
+            force=True)
+        # ejabberd should be locked still
+        self.assertTrue(ejabberd.locked)
+        self.assertTrue(ejabberd.date_locked <= datetime.utcnow())
+        self.assertEqual(ejabberd.compose.release, ejabberd.release)
+        self.assertEqual(ejabberd.compose.request, ejabberd.request)
+        # The other packages should have been left alone
+        for u in [python_nose, python_paste_deploy]:
+            self.assertFalse(u.locked)
+            self.assertIsNone(u.date_locked)
+            self.assertIsNone(u.compose)
+
     @mock.patch('bodhi.server.push.bodhi.server.notifications.init', mock.Mock())
     @mock.patch('bodhi.server.push.bodhi.server.notifications.publish')
     def test_resume_empty_compose(self, publish):