#319 Remove Bodhi dependecy
Merged 5 years ago by lholecek. Opened 5 years ago by lholecek.
lholecek/greenwave remove-bodhi-dependecy  into  master

Remove Bodhi dependecy
Lukas Holecek • 5 years ago  
file modified
+2 -3
@@ -130,9 +130,8 @@ 

     A distribution update in `Bodhi`_. Updates are identified by their Bodhi

     update id, as in ``FEDORA-2018-ec7cb4d5eb``.

  

-    A Bodhi update contains one or more Koji builds. When Greenwave makes a

-    decision about a Bodhi update, it *also* considers any policies which apply

-    to Koji builds in that update.

+    To make decision about Koji builds in the update, they need to be explicitly

+    listed in decision query.

  

  ``compose``

     A distribution compose. The compose tool (typically Pungi) takes a snapshot

file modified
+5 -34
@@ -169,23 +169,6 @@ 

  

  

  @pytest.yield_fixture(scope='session')

- def bodhi():

-     start_server_arguments = [

-         'gunicorn-3',

-         '--bind=127.0.0.1:5677',

-         '--access-logfile=-',

-         'fake_bodhi:application'

-     ]

- 

-     with server_subprocess(

-             name='bodhi',

-             port=5677,

-             source_path=os.path.dirname(__file__),

-             start_server_arguments=start_server_arguments) as url:

-         yield url

- 

- 

- @pytest.yield_fixture(scope='session')

  def distgit_server(tmpdir_factory):

      """ Creating a fake dist-git process. It is just a serving some files in a tmp dir """

      tmp_dir = tmpdir_factory.mktemp('distgit')
@@ -203,7 +186,7 @@ 

  

  

  @pytest.yield_fixture(scope='session')

- def greenwave_server(tmpdir_factory, resultsdb_server, waiverdb_server, bodhi):

+ def greenwave_server(tmpdir_factory, resultsdb_server, waiverdb_server):

      cache_file = tmpdir_factory.mktemp('greenwave').join('cache.dbm')

      settings_content = """

          CACHE = {
@@ -213,8 +196,7 @@ 

          }

          RESULTSDB_API_URL = '%sapi/v2.0'

          WAIVERDB_API_URL = '%sapi/v1.0'

-         BODHI_URL = %r

-         """ % (cache_file.strpath, resultsdb_server, waiverdb_server, bodhi)

+         """ % (cache_file.strpath, resultsdb_server, waiverdb_server)

  

      start_server_arguments = [

          'gunicorn-3',
@@ -245,11 +227,10 @@ 

      ResultsDB and WaiverDB.

      """

  

-     def __init__(self, requests_session, resultsdb_url, waiverdb_url, bodhi_url, distgit_url):

+     def __init__(self, requests_session, resultsdb_url, waiverdb_url, distgit_url):

          self.requests_session = requests_session

          self.resultsdb_url = resultsdb_url

          self.waiverdb_url = waiverdb_url

-         self.bodhi_url = bodhi_url

          self.distgit_url = distgit_url

          self._counter = itertools.count(1)

  
@@ -331,18 +312,8 @@ 

          response.raise_for_status()

          return response.json()

  

-     def create_bodhi_update(self, build_nvrs):

-         data = {'builds': [{'nvr': nvr} for nvr in build_nvrs]}

-         response = self.requests_session.post(

-             self.bodhi_url + 'updates/',

-             headers={'Content-Type': 'application/json'},

-             timeout=TEST_HTTP_TIMEOUT,

-             data=json.dumps(data))

-         response.raise_for_status()

-         return response.json()

- 

  

  @pytest.fixture(scope='session')

- def testdatabuilder(requests_session, resultsdb_server, waiverdb_server, bodhi, distgit_server):

-     return TestDataBuilder(requests_session, resultsdb_server, waiverdb_server, bodhi,

+ def testdatabuilder(requests_session, resultsdb_server, waiverdb_server, distgit_server):

+     return TestDataBuilder(requests_session, resultsdb_server, waiverdb_server,

                             distgit_server)

@@ -14,8 +14,6 @@ 

          testdatabuilder):

      load_config.return_value = {'greenwave_api_url': greenwave_server + 'api/v1.0'}

      nvr = testdatabuilder.unique_nvr(product_version='fc26')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

-     updateid = update['updateid']

      result = testdatabuilder.create_result(item=nvr,

                                             testcase_name='dist.rpmdeplint',

                                             outcome='PASSED')
@@ -44,8 +42,7 @@ 

      assert handler.topic == ['topic_prefix.environment.taskotron.result.new']

      handler.consume(message)

  

-     # We expect 4 messages altogether.

-     assert len(mock_fedmsg.mock_calls) == 4

+     assert len(mock_fedmsg.mock_calls) == 2

      assert all(call[2]['topic'] == 'decision.update' for call in mock_fedmsg.mock_calls)

      actual_msgs_sent = [call[2]['msg'] for call in mock_fedmsg.mock_calls]

      assert actual_msgs_sent[0] == {
@@ -155,117 +152,6 @@ 

              ],

          },

      }

-     assert actual_msgs_sent[2] == {

-         'policies_satisfied': False,

-         'decision_context': 'bodhi_update_push_stable',

-         'product_version': 'fedora-26',

-         'satisfied_requirements': [

-             {

-                 'result_id': result['id'],

-                 'testcase': 'dist.rpmdeplint',

-                 'type': 'test-result-passed',

-             },

-         ],

-         'unsatisfied_requirements': [

-             {

-                 'testcase': 'dist.abicheck',

-                 'item': {'item': nvr, 'type': 'koji_build'},

-                 'subject_type': 'koji_build',

-                 'subject_identifier': nvr,

-                 'type': 'test-result-missing',

-                 'scenario': None,

-             },

-             {

-                 'testcase': 'dist.upgradepath',

-                 'item': {'item': nvr, 'type': 'koji_build'},

-                 'subject_type': 'koji_build',

-                 'subject_identifier': nvr,

-                 'type': 'test-result-missing',

-                 'scenario': None,

-             }

-         ],

-         'summary': '2 of 3 required test results missing',

-         'subject': [

-             {'item': updateid, 'type': 'bodhi_update'},

-             {'item': nvr, 'type': 'koji_build'},

-             {'original_spec_nvr': nvr},

-         ],

-         'subject_type': 'bodhi_update',

-         'subject_identifier': updateid,

-         'applicable_policies': ['taskotron_release_critical_tasks_with_blacklist',

-                                 'taskotron_release_critical_tasks'],

-         'previous': {

-             'applicable_policies': ['taskotron_release_critical_tasks_with_blacklist',

-                                     'taskotron_release_critical_tasks'],

-             'policies_satisfied': False,

-             'summary': '3 of 3 required test results missing',

-             'satisfied_requirements': [],

-             'unsatisfied_requirements': [

-                 {

-                     'testcase': 'dist.abicheck',

-                     'item': {'item': nvr, 'type': 'koji_build'},

-                     'subject_type': 'koji_build',

-                     'subject_identifier': nvr,

-                     'type': 'test-result-missing',

-                     'scenario': None,

-                 },

-                 {

-                     'testcase': 'dist.rpmdeplint',

-                     'item': {'item': nvr, 'type': 'koji_build'},

-                     'subject_type': 'koji_build',

-                     'subject_identifier': nvr,

-                     'type': 'test-result-missing',

-                     'scenario': None,

-                 },

-                 {

-                     'testcase': 'dist.upgradepath',

-                     'item': {'item': nvr, 'type': 'koji_build'},

-                     'subject_type': 'koji_build',

-                     'subject_identifier': nvr,

-                     'type': 'test-result-missing',

-                     'scenario': None,

-                 },

-             ],

-         },

-     }

-     assert actual_msgs_sent[3] == {

-         'policies_satisfied': True,

-         'decision_context': 'bodhi_update_push_testing',

-         'product_version': 'fedora-*',

-         'satisfied_requirements': [

-             {

-                 'result_id': result['id'],

-                 'testcase': 'dist.rpmdeplint',

-                 'type': 'test-result-passed',

-             },

-         ],

-         'unsatisfied_requirements': [],

-         'summary': 'all required tests passed',

-         'subject': [

-             {'item': updateid, 'type': 'bodhi_update'},

-             {'item': nvr, 'type': 'koji_build'},

-             {'original_spec_nvr': nvr},

-         ],

-         'subject_type': 'bodhi_update',

-         'subject_identifier': updateid,

-         'applicable_policies': ['taskotron_release_critical_tasks_for_testing'],

-         'previous': {

-             'applicable_policies': ['taskotron_release_critical_tasks_for_testing'],

-             'policies_satisfied': False,

-             'summary': '1 of 1 required test results missing',

-             'satisfied_requirements': [],

-             'unsatisfied_requirements': [

-                 {

-                     'testcase': 'dist.rpmdeplint',

-                     'item': {'item': nvr, 'type': 'koji_build'},

-                     'subject_type': 'koji_build',

-                     'subject_identifier': nvr,

-                     'type': 'test-result-missing',

-                     'scenario': None,

-                 },

-             ],

-         },

-     }

  

  

  @mock.patch('greenwave.consumers.resultsdb.fedmsg.config.load_config')

@@ -18,8 +18,6 @@ 

          mock_fedmsg, load_config, requests_session, greenwave_server, testdatabuilder):

      load_config.return_value = {'greenwave_api_url': greenwave_server + 'api/v1.0'}

      nvr = testdatabuilder.unique_nvr()

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

-     updateid = update['updateid']

  

      failing_test = TASKTRON_RELEASE_CRITICAL_TASKS[0]

      result = testdatabuilder.create_result(
@@ -54,8 +52,7 @@ 

      assert handler.topic == ['topic_prefix.environment.waiver.new']

      handler.consume(message)

  

-     # We expect 2 messages altogether.

-     assert len(mock_fedmsg.mock_calls) == 2

+     assert len(mock_fedmsg.mock_calls) == 1

      assert all(call[2]['topic'] == 'decision.update' for call in mock_fedmsg.mock_calls)

      actual_msgs_sent = [call[2]['msg'] for call in mock_fedmsg.mock_calls]

      assert actual_msgs_sent[0] == {
@@ -117,64 +114,3 @@ 

          'summary': 'all required tests passed',

          'testcase': testcase,

      }

-     assert actual_msgs_sent[1] == {

-         'applicable_policies': ['taskotron_release_critical_tasks_with_blacklist',

-                                 'taskotron_release_critical_tasks'],

-         'policies_satisfied': True,

-         'decision_context': 'bodhi_update_push_stable',

-         'previous': {

-             'applicable_policies': ['taskotron_release_critical_tasks_with_blacklist',

-                                     'taskotron_release_critical_tasks'],

-             'policies_satisfied': False,

-             'summary': '1 of 3 required tests failed',

-             'satisfied_requirements': [

-                 {

-                     'result_id': results[0]['id'],

-                     'testcase': passing_tests[0],

-                     'type': 'test-result-passed'

-                 },

-                 {

-                     'result_id': results[1]['id'],

-                     'testcase': passing_tests[1],

-                     'type': 'test-result-passed'

-                 }

-             ],

-             'unsatisfied_requirements': [

-                 {

-                     'result_id': result['id'],

-                     'item': {'item': nvr, 'type': 'koji_build'},

-                     'testcase': TASKTRON_RELEASE_CRITICAL_TASKS[0],

-                     'type': 'test-result-failed',

-                     'scenario': None,

-                 },

-             ],

-         },

-         'product_version': 'fedora-26',

-         'subject': [

-             {'item': updateid, 'type': 'bodhi_update'},

-             {'item': nvr, 'type': 'koji_build'},

-             {'original_spec_nvr': nvr},

-         ],

-         'subject_type': 'bodhi_update',

-         'subject_identifier': updateid,

-         'satisfied_requirements': [

-             {

-                 'result_id': result['id'],

-                 'testcase': failing_test,

-                 'type': 'test-result-passed'

-             },

-             {

-                 'result_id': results[0]['id'],

-                 'testcase': passing_tests[0],

-                 'type': 'test-result-passed'

-             },

-             {

-                 'result_id': results[1]['id'],

-                 'testcase': passing_tests[1],

-                 'type': 'test-result-passed'

-             }

-         ],

-         'unsatisfied_requirements': [],

-         'summary': 'all required tests passed',

-         'testcase': testcase,

-     }

@@ -1,62 +0,0 @@ 

- 

- import re

- import json

- import hashlib

- from urllib.parse import parse_qs

- 

- 

- updates = {}  #: {id -> update info}

- 

- 

- def application(environ, start_response):

-     path_info = environ['PATH_INFO']

- 

-     m = re.match(r'/updates/(.+)$', path_info)

-     if m:

-         updateid = m.group(1)

-         if environ['REQUEST_METHOD'] == 'GET':

-             if updateid in updates:

-                 start_response('200 OK', [('Content-Type', 'application/json')])

-                 return [json.dumps({'update': updates[updateid]}).encode('utf8')]

-             else:

-                 start_response('404 Not Found', [])

-                 return []

-         else:

-             start_response('405 Method Not Allowed', [])

-             return []

- 

-     m = re.match(r'/updates/$', path_info)

-     if m:

-         if environ['REQUEST_METHOD'] == 'GET':

-             params = parse_qs(environ['QUERY_STRING'])

-             if 'builds' in params:

-                 response_updates = [u for u in updates.values()

-                                     if set(params['builds']).issubset(build['nvr']

-                                                                       for build in u['builds'])]

-             else:

-                 response_updates = list(updates.values())

-             response_data = {

-                 'page': 1,

-                 'pages': 1,

-                 'rows_per_page': len(updates),

-                 'total': len(updates),

-                 'updates': response_updates,

-             }

-             start_response('200 OK', [('Content-Type', 'application/json')])

-             return [json.dumps(response_data).encode('utf8')]

-         if environ['REQUEST_METHOD'] == 'POST':

-             body = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH']))

-             updateid = 'FEDORA-2000-{}'.format(hashlib.sha1(body).hexdigest()[-8:])

-             assert updateid not in updates

-             update = json.loads(body)

-             update['updateid'] = updateid

-             updates[updateid] = update

-             print('Fake Bodhi created new update %r' % update)

-             start_response('201 Created', [('Content-Type', 'application/json')])

-             return [json.dumps(update).encode('utf8')]  # XXX check what Bodhi really returns

-         else:

-             start_response('405 Method Not Allowed', [])

-             return []

- 

-     start_response('404 Not Found', [])

-     return []

file modified
+49 -60
@@ -162,36 +162,36 @@ 

  

  @pytest.mark.smoke

  def test_404_for_invalid_product_version(requests_session, greenwave_server, testdatabuilder):

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[testdatabuilder.unique_nvr()])

+     nvr = testdatabuilder.unique_nvr()

      data = {

          'decision_context': 'bodhi_push_update_stable',

          'product_version': 'f26',  # not a real product version

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},

                                data=json.dumps(data))

      assert r.status_code == 404

-     expected = ('Cannot find any applicable policies for bodhi_update subjects '

+     expected = ('Cannot find any applicable policies for koji_build subjects '

                  'at gating point bodhi_push_update_stable in f26')

      assert expected == r.json()['message']

  

  

  @pytest.mark.smoke

  def test_404_for_invalid_decision_context(requests_session, greenwave_server, testdatabuilder):

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[testdatabuilder.unique_nvr()])

+     nvr = testdatabuilder.unique_nvr()

      data = {

          'decision_context': 'bodhi_push_update',  # missing the _stable part!

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},

                                data=json.dumps(data))

      assert r.status_code == 404

-     expected = ('Cannot find any applicable policies for bodhi_update subjects '

+     expected = ('Cannot find any applicable policies for koji_build subjects '

                  'at gating point bodhi_push_update in fedora-26')

      assert expected == r.json()['message']

  
@@ -222,12 +222,11 @@ 

          testdatabuilder.create_result(item=nvr,

                                        testcase_name=testcase_name,

                                        outcome='PASSED')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

  

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',
@@ -251,12 +250,11 @@ 

          results.append(testdatabuilder.create_result(item=nvr,

                                                       testcase_name=testcase_name,

                                                       outcome='PASSED'))

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

          'verbose': True,

      }

  
@@ -291,12 +289,13 @@ 

              results.append(testdatabuilder.create_result(

                  item=nvr, testcase_name=testcase_name, outcome='PASSED'))

  

-     update = testdatabuilder.create_bodhi_update(build_nvrs=build_nvrs)

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject': [

+             {'type': 'koji_build', 'item': nvr}

+             for nvr in build_nvrs

+         ],

          'verbose': True,

      }

  
@@ -324,12 +323,13 @@ 

                  comment='This is fine')

              waivers.append(waiver)

  

-     update = testdatabuilder.create_bodhi_update(build_nvrs=build_nvrs)

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject': [

+             {'type': 'koji_build', 'item': nvr}

+             for nvr in build_nvrs

+         ],

          'verbose': True,

      }

  
@@ -359,12 +359,11 @@ 

          testdatabuilder.create_result(item=nvr,

                                        testcase_name=testcase_name,

                                        outcome='PASSED')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -383,12 +382,11 @@ 

      result = testdatabuilder.create_result(item=nvr,

                                             testcase_name=TASKTRON_RELEASE_CRITICAL_TASKS[0],

                                             outcome='FAILED')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -426,12 +424,11 @@ 

      result = testdatabuilder.create_result(item=nvr,

                                             testcase_name=TASKTRON_RELEASE_CRITICAL_TASKS[0],

                                             outcome='QUEUED')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -470,12 +467,11 @@ 

      result = testdatabuilder.create_result(item=nvr,

                                             testcase_name=TASKTRON_RELEASE_CRITICAL_TASKS[0],

                                             outcome='RUNNING')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -511,12 +507,11 @@ 

  

  def test_make_a_decision_on_no_results(requests_session, greenwave_server, testdatabuilder):

      nvr = testdatabuilder.unique_nvr()

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -543,13 +538,11 @@ 

  

  def test_empty_policy_is_always_satisfied(

          requests_session, greenwave_server, testdatabuilder):

-     nvr = testdatabuilder.unique_nvr()

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-24',

          'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_identifier': 'FEDORA-2000-abcdef01',

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -570,12 +563,11 @@ 

          testdatabuilder.create_result(item=nvr,

                                        testcase_name=testcase_name,

                                        outcome='PASSED')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -590,7 +582,7 @@ 

      assert res_data['unsatisfied_requirements'] == []

  

  

- def test_bodhi_nonexistent_bodhi_update(

+ def test_bodhi_nonexistent_bodhi_update_policy(

          requests_session, greenwave_server, testdatabuilder):

      nvr = testdatabuilder.unique_nvr()

      for testcase_name in TASKTRON_RELEASE_CRITICAL_TASKS:
@@ -610,8 +602,7 @@ 

      assert r.status_code == 200

      res_data = r.json()

      assert res_data['policies_satisfied'] is True

-     assert 'taskotron_release_critical_tasks' in res_data['applicable_policies']

-     assert 'taskotron_release_critical_tasks_with_blacklist' in res_data['applicable_policies']

+     assert res_data['applicable_policies'] == []

      assert res_data['summary'] == 'no tests are required'

      assert res_data['unsatisfied_requirements'] == []

  
@@ -635,12 +626,11 @@ 

          testdatabuilder.create_result(item=nvr,

                                        testcase_name=testcase_name,

                                        outcome='PASSED')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -677,12 +667,11 @@ 

          testdatabuilder.create_result(item=nvr,

                                        testcase_name=testcase_name,

                                        outcome='PASSED')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -805,12 +794,11 @@ 

          testdatabuilder.create_result(item=nvr,

                                        testcase_name=testcase_name,

                                        outcome='PASSED')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_type': 'koji_build',

+         'subject_identifier': nvr,

      }

      r_ = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                 headers={'Content-Type': 'application/json'},
@@ -864,12 +852,11 @@ 

          testdatabuilder.create_result(item=nvr,

                                        testcase_name=testcase_name,

                                        outcome='PASSED')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

          'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_identifier': 'FEDORA-2000-abcdef01',

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -903,12 +890,11 @@ 

          testdatabuilder.create_result(item=nvr,

                                        testcase_name=testcase_name,

                                        outcome='PASSED')

-     update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr])

      data = {

          'decision_context': 'bodhi_update_push_stable',

          'product_version': 'fedora-26',

          'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject_identifier': 'FEDORA-2000-abcdef01',

      }

      r = requests_session.post(greenwave_server + 'api/v1.0/decision',

                                headers={'Content-Type': 'application/json'},
@@ -1059,12 +1045,15 @@ 

              results.append(testdatabuilder.create_result(

                  item=nvr, testcase_name=testcase_name, outcome='PASSED'))

  

-     update = testdatabuilder.create_bodhi_update(build_nvrs=nvrs)

      data = {

          'decision_context': 'bodhi_update_push_stable_with_no_rules',

          'product_version': 'fedora-28',

-         'subject_type': 'bodhi_update',

-         'subject_identifier': update['updateid'],

+         'subject': [

+             {'type': 'koji_build', 'item': nvr}

+             for nvr in nvrs

+         ] + [

+             {'type': 'bodhi_update', 'item': 'FEDORA-2000-abcdef01'}

+         ],

          'verbose': True,

      }

  

file modified
+76 -92
@@ -5,7 +5,7 @@ 

  from prometheus_client import generate_latest

  from greenwave import __version__

  from greenwave.policies import summarize_answers, RemotePolicy, RemoteRule

- from greenwave.resources import ResultsRetriever, retrieve_waivers, retrieve_builds_in_update

+ from greenwave.resources import ResultsRetriever, retrieve_waivers

  from greenwave.safe_yaml import SafeYAMLError

  from greenwave.utils import insert_headers, jsonp

  from greenwave.monitoring import (
@@ -18,7 +18,27 @@ 

  api = (Blueprint('api_v1', __name__))

  

  

- def subject_list_to_type_identifier(subject):

+ def _decision_subject(subject):

+     subject_type = subject.get('type')

+     subject_identifier = subject.get('item')

+ 

+     if subject_identifier:

+         if subject_type in ('bodhi_update', 'component-version', 'koji_build'):

+             return (subject_type, subject_identifier)

+ 

+         if subject_type == 'brew-build':

+             return ('koji_build', subject_identifier)

+ 

+     if 'productmd.compose.id' in subject:

+         return ('compose', subject['productmd.compose.id'])

+ 

+     if 'original_spec_nvr' in subject:

+         return ('koji_build', subject['original_spec_nvr'])

+ 

+     raise BadRequest('Unrecognised subject type: %r' % subject)

+ 

+ 

+ def _decision_subjects_for_request(data):

      """

      Greenwave < 0.8 accepted a list of arbitrary dicts for the 'subject'.

      Now we expect a specific type and identifier.
@@ -28,29 +48,21 @@ 

      with WaiverDB < 0.11, but it accepts a single subject dict. Here we accept

      a list.

      """

-     if (not isinstance(subject, list) or not subject or

-             not all(isinstance(entry, dict) for entry in subject)):

-         raise BadRequest('Invalid subject, must be a list of dicts')

-     if any(entry.get('type') == 'bodhi_update' and 'item' in entry for entry in subject):

-         # Assume that all the other entries in the list are just for the

-         # builds which are in the Bodhi update. So really, the caller wants a

-         # decision about the Bodhi update. Ignore everything else. (Is this

-         # wise? Maybe not...)

-         identifier = [entry for entry in subject if entry.get('type') == 'bodhi_update'][0]['item']

-         return ('bodhi_update', identifier)

-     if len(subject) == 1 and 'productmd.compose.id' in subject[0]:

-         return ('compose', subject[0]['productmd.compose.id'])

-     # We don't know of any callers who would ask about subjects like this,

-     # but it's easy enough to handle here anyway:

-     if len(subject) == 1 and subject[0].get('type') == 'brew-build' and 'item' in subject[0]:

-         return ('koji_build', subject[0]['item'])

-     if len(subject) == 1 and subject[0].get('type') == 'koji_build' and 'item' in subject[0]:

-         return ('koji_build', subject[0]['item'])

-     if len(subject) == 1 and 'original_spec_nvr' in subject[0]:

-         return ('koji_build', subject[0]['original_spec_nvr'])

-     if len(subject) == 1 and subject[0].get('type') == 'component-version' and 'item' in subject[0]:

-         return ('component-version', subject[0]['item'])

-     raise BadRequest('Unrecognised subject type: %r' % subject)

+     if 'subject' in data:

+         subjects = data['subject']

+         if (not isinstance(subjects, list) or not subjects or

+                 not all(isinstance(entry, dict) for entry in subjects)):

+             raise BadRequest('Invalid subject, must be a list of dicts')

+ 

+         for subject in subjects:

+             yield _decision_subject(subject)

+     else:

+         if 'subject_type' not in data:

+             raise BadRequest('Missing required "subject_type" parameter')

+         if 'subject_identifier' not in data:

+             raise BadRequest('Missing required "subject_identifier" parameter')

+ 

+         yield data['subject_type'], data['subject_identifier']

  

  

  def subject_type_identifier_to_list(subject_type, subject_identifier):
@@ -59,19 +71,15 @@ 

      This is for backwards compatibility in emitted messages.

      """

      if subject_type == 'bodhi_update':

-         old_subject = [{'type': 'bodhi_update', 'item': subject_identifier}]

-         for nvr in retrieve_builds_in_update(subject_identifier):

-             old_subject.append({'type': 'koji_build', 'item': nvr})

-             old_subject.append({'original_spec_nvr': nvr})

-         return old_subject

-     elif subject_type == 'koji_build':

+         return [{'type': 'bodhi_update', 'item': subject_identifier}]

+     if subject_type == 'koji_build':

          return [{'type': 'koji_build', 'item': subject_identifier}]

-     elif subject_type == 'compose':

+     if subject_type == 'compose':

          return [{'productmd.compose.id': subject_identifier}]

-     elif subject_type == 'component-version':

+     if subject_type == 'component-version':

          return [{'type': 'component-version', 'item': subject_identifier}]

-     else:

-         raise BadRequest('Unrecognised subject type: %s' % subject_type)

+ 

+     raise BadRequest('Unrecognised subject type: %s' % subject_type)

  

  

  @api.route('/version', methods=['GET'])
@@ -291,20 +299,8 @@ 

              raise BadRequest('Missing required decision context')

      else:

          raise UnsupportedMediaType('No JSON payload in request')

-     data = request.get_json()

- 

-     # Greenwave < 0.8

-     if 'subject' in data:

-         data['subject_type'], data['subject_identifier'] = \

-             subject_list_to_type_identifier(data['subject'])

  

-     if 'subject_type' not in data:

-         raise BadRequest('Missing required "subject_type" parameter')

-     if 'subject_identifier' not in data:

-         raise BadRequest('Missing required "subject_identifier" parameter')

- 

-     subject_type = data['subject_type']

-     subject_identifier = data['subject_identifier']

+     data = request.get_json()

      product_version = data['product_version']

      decision_context = data['decision_context']

      verbose = data.get('verbose', False)
@@ -324,54 +320,45 @@ 

                                                "'DIST_GIT_URL_TEMPLATE' and KOJI_BASE_URL in "

                                                "your configuration.")

  

-     subject_policies = [policy for policy in current_app.config['policies']

-                         if policy.applies_to(decision_context, product_version, subject_type)]

-     if subject_type == 'bodhi_update':

-         # Also need to apply policies for each build in the update.

-         build_policies = [policy for policy in current_app.config['policies']

-                           if policy.applies_to(decision_context, product_version, 'koji_build')]

-     else:

-         build_policies = []

-     applicable_policies = subject_policies + build_policies

-     if not applicable_policies:

-         raise NotFound(

-             'Cannot find any applicable policies for %s subjects at gating point %s in %s' % (

-                 subject_type, decision_context, product_version))

- 

      answers = []

+     verbose_results = []

+     verbose_waivers = []

+     applicable_policies = []

      results_retriever = ResultsRetriever(

          cache=current_app.cache,

          ignore_results=ignore_results,

          timeout=current_app.config['REQUESTS_TIMEOUT'],

          verify=current_app.config['REQUESTS_VERIFY'],

          url=current_app.config['RESULTSDB_API_URL'])

-     waivers = retrieve_waivers(product_version, subject_type, [subject_identifier])

-     waivers = [w for w in waivers if w['id'] not in ignore_waivers]

  

-     for policy in subject_policies:

-         answers.extend(

-             policy.check(product_version, subject_identifier, results_retriever, waivers))

+     for subject_type, subject_identifier in _decision_subjects_for_request(data):

+         subject_policies = [

+             policy for policy in current_app.config['policies']

+             if policy.applies_to(decision_context, product_version, subject_type)]

+         if not subject_policies:

+             # Ignore non-existent policy for Bodhi updates.

+             if subject_type == 'bodhi_update':

+                 continue

  

-     if build_policies:

-         build_nvrs = retrieve_builds_in_update(subject_identifier)

+             raise NotFound(

+                 'Cannot find any applicable policies for %s subjects at gating point %s in %s' % (

+                     subject_type, decision_context, product_version))

  

-         nvrs_waivers = retrieve_waivers(product_version, 'koji_build', build_nvrs)

-         nvrs_waivers = [w for w in nvrs_waivers if w['id'] not in ignore_waivers]

-         waivers.extend(nvrs_waivers)

+         waivers = retrieve_waivers(product_version, subject_type, [subject_identifier])

+         waivers = [w for w in waivers if w['id'] not in ignore_waivers]

  

-         for nvr in build_nvrs:

-             nvr_waivers = [

-                 item for item in nvrs_waivers

-                 if nvr == item.get('subject_identifier')

-             ]

+         for policy in subject_policies:

+             answers.extend(

+                 policy.check(product_version, subject_identifier, results_retriever, waivers))

  

-             for policy in build_policies:

-                 answers.extend(

-                     policy.check(product_version, nvr, results_retriever, nvr_waivers))

-     else:

-         build_nvrs = []

+         applicable_policies.extend(subject_policies)

  

-     res = {

+         if verbose:

+             # Retrieve test results for all items when verbose output is requested.

+             verbose_results.extend(results_retriever.retrieve(subject_type, subject_identifier))

+             verbose_waivers.extend(waivers)

+ 

+     response = {

          'policies_satisfied': all(answer.is_satisfied for answer in answers),

          'summary': summarize_answers(answers),

          'applicable_policies': [policy.id for policy in applicable_policies],
@@ -380,17 +367,14 @@ 

          'unsatisfied_requirements':

              [answer.to_json() for answer in answers if not answer.is_satisfied],

      }

+ 

      if verbose:

-         # Retrieve test results for all items when verbose output is requested.

-         results = list(results_retriever.retrieve(subject_type, subject_identifier))

-         for nvr in build_nvrs:

-             results += results_retriever.retrieve('koji_build', nvr)

- 

-         res.update({

-             'results': results,

-             'waivers': waivers,

+         response.update({

+             'results': verbose_results,

+             'waivers': verbose_waivers,

          })

-     resp = jsonify(res)

+ 

+     resp = jsonify(response)

      resp = insert_headers(resp)

      resp.status_code = 200

      return resp

file modified
-5
@@ -16,11 +16,6 @@ 

  

      RESULTSDB_API_URL = 'https://taskotron.fedoraproject.org/resultsdb_api/api/v2.0'

      WAIVERDB_API_URL = 'https://waiverdb.fedoraproject.org/api/v1.0'

-     # Greenwave queries Bodhi to map builds <-> updates,

-     # so that it can make decisions about updates based on results for builds.

-     # If you don't have Bodhi, set this to None

-     # (this effectively disables the 'bodhi_update' subject type).

-     BODHI_URL = 'https://bodhi.fedoraproject.org/'

  

      # Options for outbound HTTP requests made by python-requests

      DIST_GIT_BASE_URL = 'https://src.fedoraproject.org/'

@@ -172,12 +172,6 @@ 

              else:

                  nvr = _decode(data['original_spec_nvr'])

              yield ('koji_build', nvr)

-             # If the result is for a build, it may also influence the decision

-             # about any update which the build is part of.

-             if current_app.config['BODHI_URL']:

-                 updateid = greenwave.resources.retrieve_update_for_build(nvr)

-                 if updateid is not None:

-                     yield ('bodhi_update', updateid)

  

      def consume(self, message):

          """

@@ -12,7 +12,6 @@ 

  import logging

  import json

  

- from flask import current_app

  import fedmsg.consumers

  import requests

  
@@ -75,13 +74,6 @@ 

          with self.flask_app.app_context():

              self._publish_decision_changes(subject_type, subject_identifier, msg['id'],

                                             product_version, testcase)

-             if subject_type == 'koji_build' and current_app.config['BODHI_URL']:

-                 # If the waiver is for a build, it may also influence the decision

-                 # about any update which the build is part of.

-                 updateid = greenwave.resources.retrieve_update_for_build(subject_identifier)

-                 if updateid is not None:

-                     self._publish_decision_changes('bodhi_update', updateid, msg['id'],

-                                                    product_version, testcase)

  

      @publish_decision_exceptions_waiver_counter.count_exceptions()

      def _publish_decision_changes(self, subject_type, subject_identifier, waiver_id,

file modified
+1 -50
@@ -12,7 +12,7 @@ 

  import requests

  import urllib3.exceptions

  

- from urllib.parse import urlparse, urljoin

+ from urllib.parse import urlparse

  import xmlrpc.client

  from flask import current_app

  from werkzeug.exceptions import BadGateway
@@ -207,55 +207,6 @@ 

      return response.content

  

  

- def retrieve_builds_in_update(update_id):

-     """

-     Queries Bodhi to find the list of builds in the given update.

-     Returns a list of build NVRs.

-     """

-     if not current_app.config['BODHI_URL']:

-         log.warning('Making a decision about Bodhi update %s '

-                     'but Bodhi integration is disabled! '

-                     'Assuming no builds in update',

-                     update_id)

-         return []

-     update_info_url = urljoin(current_app.config['BODHI_URL'],

-                               '/updates/{}'.format(update_id))

-     timeout = current_app.config['REQUESTS_TIMEOUT']

-     verify = current_app.config['REQUESTS_VERIFY']

-     response = requests_session.get(update_info_url,

-                                     headers={'Accept': 'application/json'},

-                                     timeout=timeout, verify=verify)

- 

-     # Ignore failures to retrieve Bodhi update.

-     if not response.ok:

-         log.warning(

-             'Making a decision about Bodhi update %s failed: %r',

-             update_id, response)

-         return []

- 

-     return [build['nvr'] for build in response.json()['update']['builds']]

- 

- 

- def retrieve_update_for_build(nvr):

-     """

-     Queries Bodhi to find the update which the given build is in (if any).

-     Returns a Bodhi updateid, or None if the build is not in any update.

-     """

-     updates_list_url = urljoin(current_app.config['BODHI_URL'], '/updates/')

-     params = {'builds': nvr}

-     timeout = current_app.config['REQUESTS_TIMEOUT']

-     verify = current_app.config['REQUESTS_VERIFY']

-     response = requests_session.get(updates_list_url,

-                                     params=params,

-                                     headers={'Accept': 'application/json'},

-                                     timeout=timeout, verify=verify)

-     response.raise_for_status()

-     matching_updates = response.json()['updates']

-     if matching_updates:

-         return matching_updates[0]['updateid']

-     return None

- 

- 

  # NOTE - not cached, for now.

  @greenwave.utils.retry(wait_on=urllib3.exceptions.NewConnectionError)

  def retrieve_waivers(product_version, subject_type, subject_identifiers):

@@ -11,60 +11,29 @@ 

  

  def test_announcement_keys_decode_with_list():

      cls = greenwave.consumers.resultsdb.ResultsDBHandler

-     app = greenwave.app_factory.create_app()

      message = {'msg': {'data': {

          'original_spec_nvr': ['glibc-1.0-1.fc27'],

      }}}

- 

-     with app.app_context():

-         with mock.patch('greenwave.resources.retrieve_update_for_build') as f:

-             f.return_value = None

-             subjects = list(cls.announcement_subjects(message))

+     subjects = list(cls.announcement_subjects(message))

  

      assert subjects == [('koji_build', 'glibc-1.0-1.fc27')]

  

  

- def test_announcement_subjects_include_bodhi_update():

-     cls = greenwave.consumers.resultsdb.ResultsDBHandler

-     app = greenwave.app_factory.create_app()

-     message = {'msg': {'data': {

-         'original_spec_nvr': ['glibc-1.0-2.fc27'],

-     }}}

- 

-     with app.app_context():

-         with mock.patch('greenwave.resources.retrieve_update_for_build') as f:

-             f.return_value = 'FEDORA-27-12345678'

-             subjects = list(cls.announcement_subjects(message))

- 

-     # Result was about a Koji build, but the build is in a Bodhi update.

-     # So we should announce decisions about both subjects.

-     assert subjects == [

-         ('koji_build', 'glibc-1.0-2.fc27'),

-         ('bodhi_update', 'FEDORA-27-12345678'),

-     ]

- 

- 

  def test_announcement_subjects_for_brew_build():

      # The 'brew-build' type appears internally within Red Hat. We treat it as an

      # alias of 'koji_build'.

      cls = greenwave.consumers.resultsdb.ResultsDBHandler

-     app = greenwave.app_factory.create_app()

      message = {'msg': {'data': {

          'type': 'brew-build',

          'item': ['glibc-1.0-3.fc27'],

      }}}

- 

-     with app.app_context():

-         with mock.patch('greenwave.resources.retrieve_update_for_build') as f:

-             f.return_value = None

-             subjects = list(cls.announcement_subjects(message))

+     subjects = list(cls.announcement_subjects(message))

  

      assert subjects == [('koji_build', 'glibc-1.0-3.fc27')]

  

  

  def test_announcement_subjects_for_autocloud_compose():

      cls = greenwave.consumers.resultsdb.ResultsDBHandler

-     app = greenwave.app_factory.create_app()

      message = {

          'msg': {

              'task': {
@@ -81,16 +50,11 @@ 

              }

          }

      }

- 

-     with app.app_context():

-         with mock.patch('greenwave.resources.retrieve_update_for_build') as f:

-             f.return_value = None

-             subjects = list(cls.announcement_subjects(message))

+     subjects = list(cls.announcement_subjects(message))

  

      assert subjects == [('compose', 'Fedora-AtomicHost-28_Update-20180723.1839.x86_64.qcow2')]

  

  

- @mock.patch('greenwave.resources.retrieve_update_for_build')

  @mock.patch('greenwave.resources.ResultsRetriever.retrieve')

  @mock.patch('greenwave.resources.retrieve_decision')

  @mock.patch('greenwave.resources.retrieve_scm_from_koji')
@@ -101,8 +65,7 @@ 

          mock_retrieve_yaml_remote_rule,

          mock_retrieve_scm_from_koji,

          mock_retrieve_decision,

-         mock_retrieve_results,

-         mock_retrieve_bodhi_update):

+         mock_retrieve_results):

      """

      Test publishing decision change message for test cases mentioned in

      gating.yaml.
@@ -135,7 +98,6 @@ 

          'data': {'item': nvr, 'type': 'koji_build'},

      }

      mock_retrieve_results.return_value = [result]

-     mock_retrieve_bodhi_update.return_value = None

  

      def retrieve_decision(url, data):

          #pylint: disable=unused-argument
@@ -191,7 +153,6 @@ 

      }

  

  

- @mock.patch('greenwave.resources.retrieve_update_for_build')

  @mock.patch('greenwave.resources.ResultsRetriever.retrieve')

  @mock.patch('greenwave.resources.retrieve_decision')

  @mock.patch('greenwave.resources.retrieve_scm_from_koji')
@@ -202,8 +163,7 @@ 

          mock_retrieve_yaml_remote_rule,

          mock_retrieve_scm_from_koji,

          mock_retrieve_decision,

-         mock_retrieve_results,

-         mock_retrieve_bodhi_update):

+         mock_retrieve_results):

      """

      Test publishing decision change message for test cases mentioned in

      gating.yaml.
@@ -236,7 +196,6 @@ 

          'data': {'item': nvr, 'type': 'koji_build'},

      }

      mock_retrieve_results.return_value = [result]

-     mock_retrieve_bodhi_update.return_value = None

  

      def retrieve_decision(url, data):

          #pylint: disable=unused-argument

Removes dependency on Bodhi - i.e. asking for all build from a Bodhi
update.

This removes cyclic dependency (Bodhi depends on Greenwave) and
simplifies the code.

Decision for bodhi_update no longer expands to include related
koji_build items from the Bodhi update. All builds have to be stated
explicitly in the "subject" field.

Decision change message for bodhi_update is no longer published if a
test results changes for a koji_build in the Bodhi update.

As an side effect, the formerly deprecated "subject" field (replaced
with "subject_identifier" and "subject_type") has to be used to query
for a decision on multiple koji_builds.

Fixes #298

Signed-off-by: Lukas Holecek hluk@email.cz

API documentation for subject field in decision endpoint still states that it's deprecated. Without the field you would need to make multiple requests to the endpoint.

The easiest way now is to allow asking for decision on multiple subjects. There is probably better and more consistent way to do this. So let me hear your ideas.

(I'll have to update the documentation.)

CC @bowlofeggs @sbaird

oh, _decision_subject and _decision_subjects is quite confusing... Can we rename them to distinguish them a little bit better?

Why are you removing this part?

Why not? :D We've put it here since we didn't want to raise an error if someone doesn't want to use the RemoteRule feature. We can maybe put a check in the RemoteRule class...

This block is completely unrelated to this function (which should just serve decisions). The code seems to check whether the application is configured correctly on every decision request. It could done after loading policies and configuration.

BTW, the code is very awkward :) -- I would expect something like:

if ('DIST_GIT_BASE_URL' not in current_app.config or
    'DIST_GIT_URL_TEMPLATE' not in current_app.config or
    'KOJI_BASE_URL' not in current_app.config or 
        any(isinstance(rule, RemoteRule) for ...):
    raise InternalServerError(...)

As I mentioned in comment above, 'subject' (deprecated field since v0.8) is still needed if we want do make decision about multiple bodhi builds in one request.

rebased onto 483d23bc395bb0a29a1ab564c052bd983f821bf7

5 years ago

This block is completely unrelated to this function (which should just serve decisions). The code seems to check whether the application is configured correctly on every decision request. It could done after loading policies and configuration.
BTW, the code is very awkward :) -- I would expect something like:
if ('DIST_GIT_BASE_URL' not in current_app.config or
'DIST_GIT_URL_TEMPLATE' not in current_app.config or
'KOJI_BASE_URL' not in current_app.config or
any(isinstance(rule, RemoteRule) for ...):
raise InternalServerError(...)

so great that you changed it

rebased onto 4c6f92657a07489657378285d9c785d7830f9245

5 years ago

rebased onto 8658b9d

5 years ago

Pull-Request has been merged by lholecek

5 years ago