Repository: fedora-infra/bodhi

Issue:
The CLI should display if an update has been waived
Similar to #2266, the CLI should display waiver information on updates.


Discussion:
To clarify, this change should be done on the ```edit``` and ```query``` subcommands.
On Sun, 2018-12-02 at 19:04 +0100, @sebwoj wrote:
> Where can I find that infomration?
> New models.update field(bodhi can set this field to true after request to "API waive update GW test")?
> Or when update have some failed greenvaive tests and test_gating_status is waiting(waived_update information should be cleared after second GW test are passed and update_test_gating_status is called)?

This may take some research. I think the Greenwave response might have the waiver information in it, but if it doesn't we may need to query WaiverDB instead.
In greenwave repo I found something usefull:
https://pagure.io/greenwave/blob/master/f/greenwave/api_v1.py#_253
How waiver information should be printed?
1. Bool value:

```
CI Status: no tests are required
   Waived: True
  Request: testing
```

2. Same as for example: comments:
```
    Comments: bodhi - 2018-12-08 14:27:51 (karma 0)
              This update has been submitted for testing by ignatenkobrain.
              bodhi - 2018-12-09 04:46:51 (karma 0)
              This update has been pushed to testing.
              bodhi - 2018-12-10 16:25:51 (karma 0)
              ignatenkobrain edited this update.  New build(s):  - meson-
              latest-20181209203125.06d0a27d  Removed build(s):  - meson-
              latest-20181208132651.06d0a27d  Karma has been reset.
              bodhi - 2018-12-10 16:25:52 (karma 0)
              This update has been submitted for testing by ignatenkobrain.

```
Example:
```
CI Status: no tests are required
  Waivers:
                  username: ralph
                  comment': This is fine.
                  product_version: fedora-27
                  time: 2018-12-08 14:27:51
                  proxied_by: null,
                  subject_type: koji_build
                  subject_identifier: cross-gcc-7.0.1-0.3.fc26
                  testcase: dist.rpmlint
  Request: testing
```
I this case, what information do we need from GW response?
Hey @sebwoj!

Good question, I'm not totally sure what all would be useful. Maybe all of the above except for the proxied_by, subject_type, and product_version fields? We don't have to get it perfect on the first try, if we want to add one of those later we can. Thanks for helping out, and I hope you enjoy your holidays if you celebrate any!

Resolving patch:
diff --git a/bodhi/client/bindings.py b/bodhi/client/bindings.py
--- a/bodhi/client/bindings.py
+++ b/bodhi/client/bindings.py
@@ -830,14 +830,34 @@ def update_str(self, update, minimal=False):
 
         if test_status:
             info = None
+            waivers = None
             if 'errors' in test_status:
                 info = '\n'.join([el.description for el in test_status.errors])
             elif 'decision' in test_status:
                 info = test_status.decision.summary
+                waivers = test_status.decision.waivers
             else:
                 log.debug('No `errors` nor `decision` in the data returned')
             if info:
                 update_lines.append(line_formatter.format('CI Status', info))
+            if waivers:
+                waivers_lines = []
+                for waiver in waivers:
+                    dt = datetime.datetime.strptime(waiver['timestamp'], '%Y-%m-%dT%H:%M:%S.%f')
+                    waivers_lines.append('{0} - {1}'.format(
+                        waiver['username'], dt.strftime('%Y-%m-%d %H:%M:%S')))
+                    waivers_lines += wrap_line(waiver['comment'])
+                    waivers_lines.append('build: {}'.format(waiver['subject_identifier']))
+                    waivers_lines.append('testcase: {}'.format(waiver['testcase']))
+
+                update_lines.append(line_formatter.format('Waivers', waivers_lines[0]))
+                waiver_line_formatter = line_formatter.replace(': ', '  ')
+                update_lines += [
+                    waiver_line_formatter.format(indent, line)
+                    for indent, line in six.moves.zip(
+                        itertools.repeat(' ', len(waivers_lines) - 1),
+                        waivers_lines[1:])
+                ]
 
         if update['request'] is not None:
             update_lines.append(line_formatter.format('Request', update['request']))
diff --git a/bodhi/server/models.py b/bodhi/server/models.py
--- a/bodhi/server/models.py
+++ b/bodhi/server/models.py
@@ -2025,7 +2025,8 @@ def get_test_gating_info(self):
         data = {
             'product_version': self.product_version,
             'decision_context': decision_context,
-            'subject': self.greenwave_subject
+            'subject': self.greenwave_subject,
+            'verbose': True,
         }
         api_url = '{}/decision'.format(config.get('greenwave_api_url'))
 


Test patch:
diff --git a/bodhi/tests/client/test___init__.py b/bodhi/tests/client/test___init__.py
--- a/bodhi/tests/client/test___init__.py
+++ b/bodhi/tests/client/test___init__.py
@@ -2609,7 +2609,8 @@ def test_waive_all(self, send_request):
             munch.Munch({
                 'decision': munch.Munch({
                     'summary': 'All tests passed',
-                    'unsatisfied_requirements': []
+                    'unsatisfied_requirements': [],
+                    'waivers': [],
                 }),
             })
         ]
@@ -2655,7 +2656,8 @@ def test_waive_some(self, send_request):
             munch.Munch({
                 'decision': munch.Munch({
                     'summary': 'All tests passed',
-                    'unsatisfied_requirements': []
+                    'unsatisfied_requirements': [],
+                    'waivers': [],
                 }),
             })
         ]
diff --git a/bodhi/tests/client/test_bindings.py b/bodhi/tests/client/test_bindings.py
--- a/bodhi/tests/client/test_bindings.py
+++ b/bodhi/tests/client/test_bindings.py
@@ -1350,12 +1350,39 @@ def test_ci_status(self, get_test_status):
         client = bindings.BodhiClient()
         client.base_url = 'http://example.com/tests/'
         get_test_status.return_value = munch.Munch(
-            {'decision': munch.Munch({'summary': 'no tests required'})})
+            {'decision': munch.Munch({'summary': 'no tests required', 'waivers': []})}
+        )
 
         text = client.update_str(client_test_data.EXAMPLE_UPDATE_MUNCH)
 
         self.assertIn('CI Status: no tests required\n', text)
 
+    @mock.patch('bodhi.client.bindings.BodhiClient.get_test_status')
+    def test_waived_tests(self, get_test_status):
+        """Ensure that information about waived tests is rendered"""
+        client = bindings.BodhiClient()
+        client.base_url = 'http://example.com/tests/'
+        get_test_status.return_value = munch.Munch(
+            {'decision': munch.Munch({
+                'summary': 'no tests required',
+                'waivers': [{'comment': 'This is fine. See BZ#1566485', 'id': 150,
+                             'product_version': 'fedora-28', 'proxied_by': None,
+                             'subject': {'item': 'slop-7.4-1.fc28', 'type': 'koji_build'},
+                             'subject_identifier': 'slop-7.4-1.fc28', 'subject_type': 'koji_build',
+                             'testcase': 'dist.rpmlint', 'timestamp': '2018-06-29T00:20:20.425844',
+                             'username': 'netvor', 'waived': True}]})}
+        )
+
+        text = client.update_str(client_test_data.EXAMPLE_UPDATE_MUNCH)
+
+        self.assertIn(
+            '     Waivers: netvor - 2018-06-29 00:20:20\n'
+            '              This is fine. See BZ#1566485\n'
+            '              build: slop-7.4-1.fc28\n'
+            '              testcase: dist.rpmlint\n',
+            text,
+        )
+
     @mock.patch.dict(
         client_test_data.EXAMPLE_UPDATE_MUNCH,
         {'notes': 'This note contains:\n* multiline formatting\n* bullet points\n\n'})
diff --git a/bodhi/tests/server/scripts/test_check_policies.py b/bodhi/tests/server/scripts/test_check_policies.py
--- a/bodhi/tests/server/scripts/test_check_policies.py
+++ b/bodhi/tests/server/scripts/test_check_policies.py
@@ -59,7 +59,9 @@ def test_policies_satisfied(self):
                 {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                 {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                 {'item': u'FEDORA-{}-a3bbe1a8f2'.format(datetime.datetime.utcnow().year),
-                 'type': 'bodhi_update'}]}
+                 'type': 'bodhi_update'}],
+            'verbose': True
+        }
         mock_greenwave.assert_called_once_with(config['greenwave_api_url'] + '/decision',
                                                expected_query)
 
@@ -92,7 +94,9 @@ def test_policies_pending_satisfied(self):
                 {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                 {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                 {'item': u'FEDORA-{}-a3bbe1a8f2'.format(datetime.datetime.utcnow().year),
-                 'type': 'bodhi_update'}]}
+                 'type': 'bodhi_update'}],
+            'verbose': True,
+        }
         mock_greenwave.assert_called_once_with(config['greenwave_api_url'] + '/decision',
                                                expected_query)
 
@@ -133,7 +137,9 @@ def test_policies_unsatisfied(self):
                 {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                 {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                 {'item': u'FEDORA-{}-a3bbe1a8f2'.format(datetime.datetime.utcnow().year),
-                 'type': 'bodhi_update'}]}
+                 'type': 'bodhi_update'}],
+            'verbose': True
+        }
         mock_greenwave.assert_called_once_with(config['greenwave_api_url'] + '/decision',
                                                expected_query)
 
@@ -164,7 +170,9 @@ def test_no_policies_enforced(self):
                 {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                 {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                 {'item': u'FEDORA-{}-a3bbe1a8f2'.format(datetime.datetime.utcnow().year),
-                 'type': 'bodhi_update'}]}
+                 'type': 'bodhi_update'}],
+            'verbose': True
+        }
         mock_greenwave.assert_called_once_with(config['greenwave_api_url'] + '/decision',
                                                expected_query)
 
@@ -195,7 +203,9 @@ def test_pushed_update(self):
             'subject': [{'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                         {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                         {'item': u'FEDORA-{}-a3bbe1a8f2'.format(datetime.datetime.utcnow().year),
-                         'type': 'bodhi_update'}]}
+                         'type': 'bodhi_update'}],
+            'verbose': True
+        }
         mock_greenwave.assert_called_once_with(config['greenwave_api_url'] + '/decision',
                                                expected_query)
 
@@ -225,7 +235,9 @@ def test_unrestricted_policy(self):
                 {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                 {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                 {'item': u'FEDORA-{}-a3bbe1a8f2'.format(datetime.datetime.utcnow().year),
-                 'type': 'bodhi_update'}]}
+                 'type': 'bodhi_update'}],
+            'verbose': True
+        }
         mock_greenwave.assert_called_once_with(config['greenwave_api_url'] + '/decision',
                                                expected_query)
 
diff --git a/bodhi/tests/server/services/test_updates.py b/bodhi/tests/server/services/test_updates.py
--- a/bodhi/tests/server/services/test_updates.py
+++ b/bodhi/tests/server/services/test_updates.py
@@ -5488,7 +5488,8 @@ def test_waive_test_results_1_unsatisfied_requirement(
                     {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                     {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                     {'item': up.alias, 'type': 'bodhi_update'}
-                ]
+                ],
+                'verbose': True,
             }
         )
 
@@ -5561,7 +5562,8 @@ def test_waive_test_results_2_unsatisfied_requirements(
                     {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                     {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                     {'item': up.alias, 'type': 'bodhi_update'}
-                ]
+                ],
+                'verbose': True,
             }
         )
 
@@ -5654,7 +5656,8 @@ def test_waive_test_results_1_of_2_unsatisfied_requirements(
                     {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                     {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                     {'item': up.alias, 'type': 'bodhi_update'}
-                ]
+                ],
+                'verbose': True,
             }
         )
 
@@ -5731,7 +5734,8 @@ def test_waive_test_results_2_of_2_unsatisfied_requirements(
                     {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                     {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                     {'item': up.alias, 'type': 'bodhi_update'}
-                ]
+                ],
+                'verbose': True,
             }
         )
 
@@ -5824,7 +5828,8 @@ def test_waive_test_results_unfailing_tests(
                     {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                     {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                     {'item': up.alias, 'type': 'bodhi_update'}
-                ]
+                ],
+                'verbose': True,
             }
         )
 
@@ -5977,7 +5982,8 @@ def test_get_test_results_erroring_on_greenwave(self, call_api, *args):
                     {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                     {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                     {'item': update.alias, 'type': 'bodhi_update'}
-                ]
+                ],
+                'verbose': True,
             },
             method='POST',
             retries=3,
@@ -6019,7 +6025,8 @@ def test_get_test_results_timing_out_on_greenwave(self, call_api, *args):
                     {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                     {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                     {'item': update.alias, 'type': 'bodhi_update'}
-                ]
+                ],
+                'verbose': True,
             },
             method='POST',
             retries=3,
@@ -6060,7 +6067,8 @@ def test_get_test_results_calling_greenwave(self, call_api, *args):
                     {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                     {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                     {'item': update.alias, 'type': 'bodhi_update'}
-                ]
+                ],
+                'verbose': True,
             },
             method='POST',
             retries=3,
@@ -6093,7 +6101,8 @@ def test_get_test_results_calling_greenwave_no_session(self, call_api, *args):
                     {'item': u'bodhi-2.0-1.fc17', 'type': 'koji_build'},
                     {'original_spec_nvr': u'bodhi-2.0-1.fc17'},
                     {'item': update.alias, 'type': 'bodhi_update'}
-                ]
+                ],
+                'verbose': True,
             },
             method='POST',
             retries=3,
diff --git a/devel/ci/integration/tests/test_bodhi_cli.py b/devel/ci/integration/tests/test_bodhi_cli.py
--- a/devel/ci/integration/tests/test_bodhi_cli.py
+++ b/devel/ci/integration/tests/test_bodhi_cli.py
@@ -282,7 +282,8 @@ def test_updates_query_details(bodhi_container, db_container, greenwave_containe
                 for b in update.builds
             ] + [
                 {"item": update.alias, "type": "bodhi_update"}
-            ]
+            ],
+            "verbose": True,
         }),
     ).json()
     assert "summary" in greenwave_result