Repository: fedora-infra/bodhi

Issue:
Allow to query Releases using Graphql
We want to query all releases using the `/graphql` endpoint.

Steps : 

- [x] Add grapqhql-sqlalchemy to the dev environment
- [x] Configure `/graphql` endpoint to use graphiql (see https://docs.graphene-python.org/projects/sqlalchemy/en/latest/tutorial/#creating-graphql-and-graphiql-views-in-flask) need to research for a generic solution or https://github.com/graphql-python/webob-graphql.
- [x] Create a new graphql_schemas.py module under `bodhi/server`
- [x] Add a Release class to the graphql_schemas.py using graphql-sqlalchmy examples
- [x] Add a Query class to the graphql service
- [x] Within the Query class add a method to query all releases (use the graphql_schemas.py class)
- [x] Test everything :smile: 

Expected results:

Using graphiql query all the releases, expected output is the same as `http https://bodhi.fedoraproject.org/releases/`


Discussion:

Resolving patch:
diff --git a/bodhi/server/config.py b/bodhi/server/config.py
--- a/bodhi/server/config.py
+++ b/bodhi/server/config.py
@@ -398,9 +398,6 @@ class BodhiConfig(dict):
         'initial_bug_msg': {
             'value': '%s has been submitted as an update to %s. %s',
             'validator': str},
-        'graphiql_enabled': {
-            'value': False,
-            'validator': _validate_bool},
         'greenwave_api_url': {
             'value': 'https://greenwave-web-greenwave.app.os.fedoraproject.org/api/v1.0',
             'validator': _validate_rstripped_str},
diff --git a/bodhi/server/graphql_schemas.py b/bodhi/server/graphql_schemas.py
deleted file mode 100644
--- a/bodhi/server/graphql_schemas.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright © 2020 Red Hat Inc., and others.
-#
-# This file is part of Bodhi.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-"""Defines schemas related to GraphQL objects."""
-from graphene import relay, Field, String
-from graphene_sqlalchemy import SQLAlchemyObjectType
-
-from bodhi.server.models import (
-    Release as ReleaseModel,
-    Update as UpdateModel,
-    BuildrootOverride as BuildrootOverrideModel
-)
-
-
-class Release(SQLAlchemyObjectType):
-    """Type object representing a distribution release from bodhi.server.models like Fedora 27."""
-
-    class Meta:
-        """Allow to set different options to the class."""
-
-        model = ReleaseModel
-        interfaces = (relay.Node, )
-    state = Field(String)
-    package_manager = Field(String)
-
-
-class Update(SQLAlchemyObjectType):
-    """Type object representing an update from bodhi.server.models."""
-
-    class Meta:
-        """Allow to set different options to the class."""
-
-        model = UpdateModel
-        interfaces = (relay.Node, )
-    status = Field(String)
-    request = Field(String)
-    date_approved = Field(String)
-
-
-class BuildrootOverride(SQLAlchemyObjectType):
-    """Type object representing an update from bodhi.server.models."""
-
-    class Meta:
-        """Allow to set different options to the class."""
-
-        model = BuildrootOverrideModel
-        interfaces = (relay.Node, )
-    submitter = Field(String)
diff --git a/bodhi/server/services/graphql.py b/bodhi/server/services/graphql.py
deleted file mode 100644
--- a/bodhi/server/services/graphql.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# Copyright © 2020 Red Hat Inc., and others.
-#
-# This file is part of Bodhi.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-"""Defines API endpoints related to GraphQL objects."""
-import graphene
-from cornice import Service
-from webob_graphql import serve_graphql_request
-
-from bodhi.server.config import config
-from bodhi.server.models import Build as BuildModel, User as UserModel
-from bodhi.server.graphql_schemas import (
-    Release,
-    ReleaseModel,
-    Update,
-    UpdateModel,
-    BuildrootOverride,
-    BuildrootOverrideModel
-)
-
-graphql = Service(name='graphql', path='/graphql', description='graphql service')
-
-
-@graphql.get()
-@graphql.post()
-def graphql_get(request):
-    """
-    Perform a GET request.
-
-    Args:
-        request (pyramid.Request): The current request.
-    Returns:
-        The GraphQL response to the request.
-    """
-    context = {'session': request.session}
-    return serve_graphql_request(
-        request, schema, graphiql_enabled=config.get('graphiql_enabled'),
-        context_value=context)
-
-
-class Query(graphene.ObjectType):
-    """Allow querying objects."""
-
-    allReleases = graphene.List(Release)
-    getReleases = graphene.Field(
-        lambda: graphene.List(Release), name=graphene.String(),
-        id_prefix=graphene.String(), composed_by_bodhi=graphene.Boolean(),
-        state=graphene.String())
-
-    getUpdates = graphene.Field(
-        lambda: graphene.List(Update), stable_karma=graphene.Int(),
-        stable_days=graphene.Int(), unstable_karma=graphene.Int(),
-        status=graphene.String(), request=graphene.String(),
-        pushed=graphene.Boolean(), critpath=graphene.Boolean(),
-        date_approved=graphene.String(), alias=graphene.String(),
-        user_id=graphene.Int(), release_name=graphene.String())
-
-    getBuildrootOverrides = graphene.Field(
-        lambda: graphene.List(BuildrootOverride),
-        submission_date=graphene.DateTime(),
-        expiration_date=graphene.DateTime(),
-        build_nvr=graphene.String(),
-        submitter_username=graphene.String())
-
-    def resolve_allReleases(self, info):
-        """Answer Queries by fetching data from the Schema."""
-        query = Release.get_query(info)  # SQLAlchemy query
-        return query.all()
-
-    def resolve_getReleases(self, info, **args):
-        """Answer Release queries with a given argument."""
-        query = Release.get_query(info)
-
-        id_prefix = args.get("id_prefix")
-        if id_prefix is not None:
-            query = query.filter(ReleaseModel.id_prefix == id_prefix)
-
-        name = args.get("name")
-        if name is not None:
-            query = query.filter(ReleaseModel.name == name)
-
-        composed_by_bodhi = args.get("composed_by_bodhi")
-        if composed_by_bodhi is not None:
-            query = query.filter(ReleaseModel.composed_by_bodhi == composed_by_bodhi)
-
-        state = args.get("state")
-        if state is not None:
-            query = query.filter(ReleaseModel.state == state)
-
-        return query.all()
-
-    def resolve_getUpdates(self, info, **args):
-        """Answer Release queries with a given argument."""
-        query = Update.get_query(info)
-
-        stable_karma = args.get("stable_karma")
-        if stable_karma is not None:
-            query = query.filter(UpdateModel.stable_karma == stable_karma)
-
-        stable_days = args.get("stable_days")
-        if stable_days is not None:
-            query = query.filter(UpdateModel.stable_days == stable_days)
-
-        unstable_karma = args.get("unstable_karma")
-        if unstable_karma is not None:
-            query = query.filter(UpdateModel.unstable_karma == unstable_karma)
-
-        status = args.get("status")
-        if status is not None:
-            query = query.filter(UpdateModel.status == status)
-
-        request = args.get("request")
-        if request is not None:
-            query = query.filter(UpdateModel.request == request)
-
-        pushed = args.get("pushed")
-        if pushed is not None:
-            query = query.filter(UpdateModel.pushed == pushed)
-
-        critpath = args.get("critpath")
-        if critpath is not None:
-            query = query.filter(UpdateModel.critpath == critpath)
-
-        date_approved = args.get("date_approved")
-        if date_approved is not None:
-            query = query.filter(UpdateModel.date_approved == date_approved)
-
-        alias = args.get("alias")
-        if alias is not None:
-            query = query.filter(UpdateModel.alias == alias)
-
-        user_id = args.get("user_id")
-        if user_id is not None:
-            query = query.filter(UpdateModel.user_id == user_id)
-
-        release_name = args.get("release_name")
-        if release_name is not None:
-            query = query.join(UpdateModel.release).filter(ReleaseModel.name == release_name)
-
-        return query.all()
-
-    def resolve_getBuildrootOverrides(self, info, **args):
-        """Answer Release queries with a given argument."""
-        query = BuildrootOverride.get_query(info)
-
-        submission_date = args.get("submission_date")
-        if submission_date is not None:
-            query = query.filter(BuildrootOverrideModel.submission_date == submission_date)
-
-        expiration_date = args.get("expiration_date")
-        if expiration_date is not None:
-            query = query.filter(BuildrootOverrideModel.expiration_date == expiration_date)
-
-        build_nvr = args.get("build_nvr")
-        if build_nvr is not None:
-            query = query.join(BuildrootOverrideModel.build).filter(BuildModel.nvr == build_nvr)
-
-        submitter_username = args.get("submitter_username")
-        if submitter_username is not None:
-            query = query.join(BuildrootOverrideModel.submitter).filter(
-                UserModel.name == submitter_username)
-
-        return query.all()
-
-
-schema = graphene.Schema(query=Query)


Test patch:
diff --git a/bodhi/tests/server/services/test_graphql.py b/bodhi/tests/server/services/test_graphql.py
deleted file mode 100644
--- a/bodhi/tests/server/services/test_graphql.py
+++ /dev/null
@@ -1,251 +0,0 @@
-# Copyright © 2020 Red Hat, Inc. and others.
-#
-# This file is part of Bodhi.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-import datetime
-
-from graphene.test import Client
-
-from bodhi.tests.server import base
-from bodhi.server.services.graphql import schema
-from bodhi.server import models
-
-
-class TestGraphQLService(base.BasePyTestCase):
-    """This class contains tests for a /graphql endpoint."""
-    def test_get(self):
-        """Ensure that a GraphQL response is returned"""
-        res = self.app.get('/graphql?query={%0A%20 allReleases{%0A%20%20%20 name%0A%20 }%0A}')
-        assert res.body == b'{"data":{"allReleases":[{"name":"F17"}]}}'
-
-    def test_allReleases(self):
-        """Testing allReleases."""
-        base.BaseTestCaseMixin.create_release(self, version='22')
-        client = Client(schema)
-        self.db.commit()
-
-        executed = client.execute("""{  allReleases{  name  }}""")
-        assert executed == {
-            "data": {
-                "allReleases": [{
-                    "name": "F17"
-                }, {
-                    "name": "F22"
-                }]
-            }
-        }
-
-    def test_enumfields(self):
-        """Testing enum fields on releases."""
-        base.BaseTestCaseMixin.create_release(self, version='22')
-        client = Client(schema)
-        self.db.commit()
-
-        executed = client.execute("""{  allReleases{  state    packageManager  }}""")
-        assert executed == {
-            'data': {
-                'allReleases': [{
-                    'packageManager': 'unspecified',
-                    'state': 'current'
-                }, {
-                    'packageManager': 'unspecified',
-                    'state': 'current'
-                }]
-            }
-        }
-
-    def test_getReleases(self):
-        """Testing getReleases query."""
-        base.BaseTestCaseMixin.create_release(self, version='22')
-        client = Client(schema)
-        self.db.commit()
-
-        executed = client.execute("""{  getReleases(idPrefix: "FEDORA"){  name  }}""")
-        assert executed == {
-            "data": {
-                "getReleases": [{
-                    "name": "F17"
-                }, {
-                    "name": "F22"
-                }]
-            }
-        }
-
-        executed = client.execute("""{  getReleases(name: "F17"){  id  }}""")
-        assert executed == {
-            "data": {
-                "getReleases": [{
-                    "id": "UmVsZWFzZTox"
-                }]
-            }
-        }
-
-        executed = client.execute("""{  getReleases(composedByBodhi: true){  name  }}""")
-        assert executed == {
-            "data": {
-                "getReleases": [{
-                    "name": "F17"
-                }, {
-                    "name": "F22"
-                }]
-            }
-        }
-
-        executed = client.execute(
-            """{  getReleases(state: "current", composedByBodhi: true){  name  }}""")
-        assert executed == {
-            "data": {
-                "getReleases": [{
-                    "name": "F17"
-                }, {
-                    "name": "F22"
-                }]
-            }
-        }
-
-    def test_getUpdates(self):
-        """Testing getUpdates query."""
-        release = base.BaseTestCaseMixin.create_release(self, version='22')
-        self.create_update(build_nvrs=['TurboGears-2.1-1.el5'],
-                           release_name=release.name)
-        up2 = self.create_update(build_nvrs=['freetype-2.10.2-1.fc32'],
-                                 release_name=release.name)
-        up2.alias = "FEDORA-2020-3223f9ec8b"
-        up2.stable_days = 1
-        up2.date_approved = datetime.datetime(2019, 10, 13, 16, 16, 22, 438484)
-        self.db.commit()
-        client = Client(schema)
-
-        executed = client.execute("""{  getUpdates(stableDays: 1,
-                                  dateApproved: "2019-10-13 16:16:22.438484")
-                                  {  alias  request  unstableKarma  }}""")
-        assert executed == {
-            "data": {
-                "getUpdates": [{
-                    "alias": "FEDORA-2020-3223f9ec8b",
-                    "request": "testing",
-                    "unstableKarma": -3
-                }]
-            }
-        }
-
-        executed = client.execute("""{  getUpdates(stableKarma: 3, status: "pending",
-                                  critpath: false, pushed: false, request:"testing"){  stableDays
-                                  userId  }}""")
-        assert executed == {
-            'data': {
-                'getUpdates': [{
-                    'stableDays': 0,
-                    'userId': 1
-                }, {
-                    'stableDays': 0,
-                    'userId': 1
-                }, {
-                    'stableDays': 1,
-                    'userId': 1
-                }]
-            }
-        }
-
-        executed = client.execute("""{  getUpdates(stableDays: 1,
-                                  unstableKarma: -3, alias: "FEDORA-2020-3223f9ec8b")
-                                  {  dateApproved  request  }}""")
-        assert executed == {
-            'data': {
-                'getUpdates': [{
-                    'dateApproved': "2019-10-13 16:16:22.438484",
-                    'request': 'testing'
-                }]
-            }
-        }
-
-        executed = client.execute("""{  getUpdates(critpath: false, stableDays: 1,
-                                  userId: 1){  request    unstableKarma  }}""")
-        assert executed == {
-            'data': {
-                'getUpdates': [{
-                    'request': 'testing',
-                    'unstableKarma': -3,
-                }]
-            }
-        }
-
-        executed = client.execute("""{  getUpdates(releaseName: "F22"){  request  }}""")
-        assert executed == {
-            'data': {
-                'getUpdates': [{
-                    'request': 'testing',
-                }, {
-                    'request': 'testing',
-                }]
-            }
-        }
-
-    def test_getBuildrootOverrides(self):
-        """Testing getBuildOverrides query."""
-        release = models.Release.get('F17')
-
-        package = models.RpmPackage(name='just-testing')
-        self.db.add(package)
-        build = models.RpmBuild(nvr='just-testing-1.0-2.fc17', package=package, release=release)
-        self.db.add(build)
-        another_user = models.User(name='aUser')
-        self.db.add(another_user)
-
-        expiration_date = datetime.datetime(2020, 10, 13, 16, 16, 22, 438484)
-        submission_date = datetime.datetime(2020, 10, 12, 16, 16, 22, 438484)
-
-        override = models.BuildrootOverride(build=build, submitter=another_user,
-                                            notes='Crazy! 😱',
-                                            expiration_date=expiration_date,
-                                            submission_date=submission_date)
-        self.db.add(override)
-        self.db.flush()
-        client = Client(schema)
-        self.db.commit()
-
-        executed = client.execute("""{  getBuildrootOverrides(buildNvr: "just-testing-1.0-2.fc17")
-                                  {  submissionDate  expirationDate  }}""")
-        assert executed == {
-            'data': {
-                'getBuildrootOverrides': [{
-                    'expirationDate': "2020-10-13T16:16:22.438484",
-                    'submissionDate': "2020-10-12T16:16:22.438484",
-                }]
-            }
-        }
-
-        executed = client.execute("""{  getBuildrootOverrides(submissionDate: "2020-10-12T16:16:22.438484",
-                                  expirationDate: "2020-10-13T16:16:22.438484")
-                                  {  notes  }}""")
-        assert executed == {
-            'data': {
-                'getBuildrootOverrides': [{
-                    'notes': "Crazy! 😱"
-                }]
-            }
-        }
-
-        executed = client.execute("""{  getBuildrootOverrides(submitterUsername: "aUser",
-                                  expirationDate: "2020-10-13T16:16:22.438484")
-                                  {  notes  }}""")
-        assert executed == {
-            'data': {
-                'getBuildrootOverrides': [{
-                    'notes': "Crazy! 😱"
-                }]
-            }
-        }
diff --git a/devel/ci/integration/tests/test_bodhi_tasks.py b/devel/ci/integration/tests/test_bodhi_tasks.py
--- a/devel/ci/integration/tests/test_bodhi_tasks.py
+++ b/devel/ci/integration/tests/test_bodhi_tasks.py
@@ -120,7 +120,9 @@ def find_bug():
     update_alias = find_update()
     bug_id = find_bug()
     # Remove previous task results
-    bodhi_container.execute(["find", "/srv/celery-results", "-type", "f", "-delete"])
+    # bodhi_container.execute(["find", "/srv/celery-results", "-type", "f", "-delete"])
+    celery_tasks = bodhi_container.execute(["ls", "/srv/celery-results"])
+    print(list(celery_tasks))
     cmd = [
         "bodhi",
         "updates",
@@ -138,6 +140,8 @@ def find_bug():
         bug_id,
         update_alias,
     ]
+    celery_tasks = bodhi_container.execute(["ls", "/srv/celery-results"])
+    print(list(celery_tasks))
     try:
         bodhi_container.execute(cmd)
     except ConuException as e:
@@ -145,7 +149,7 @@ def find_bug():
             print(log.read())
         assert False, str(e)
     try:
-        bodhi_container.execute(["wait-for-file", "-d", "/srv/celery-results"])
+        bodhi_container.execute(["wait-for-file", "/srv/celery-results"])
     except ConuException as e:
         print(f"Waiting for celery results failed, relevant update: {update_alias}")
         raise e