#232 bugzilla: introduce BUGZILLA_API_KEY
Closed 4 months ago by kparal. Opened 4 months ago by kparal.

file modified
+5 -3
@@ -59,12 +59,14 @@ 

  if os.getenv('DEBUG') == 'true':

      app.config["DEBUG"] = True

  

- # Make sure config URLs end with a slash, so that we have them all in an

- # expected format

- for key in ['BUGZILLA_URL', 'BODHI_URL', 'FAS_BASE_URL', 'PAGURE_URL', 'PAGURE_API']:

+ # Make sure config URLs end with a slash, so we have them always in the same format

+ for key in ['BODHI_URL', 'FAS_BASE_URL', 'PAGURE_URL', 'PAGURE_API']:

      if not app.config[key].endswith('/'):

          app.config[key] = app.config[key] + '/'

  

+ # Unfortunately BUGZILLA_URL mustn't end with a slash, otherwise bugzilla.Bugzilla() init fails

+ app.config['BUGZILLA_URL'] = app.config['BUGZILLA_URL'].rstrip('/')

+ 

  

  def setup_logging():

      formatter = logging.Formatter(

file modified
+4 -9
@@ -147,14 +147,12 @@ 

      config_file.write("SQLALCHEMY_DATABASE_URI = '%s'\n" % dburi or '')

      # leave places for the other config values generally needed

      config_file.write("FAS_ADMIN_GROUP = ''\n")

-     config_file.write("FAS_USER = ''\n")

-     config_file.write("FAS_PASSWORD = ''\n")

      config_file.write("PAGURE_REPO_TOKEN = ''\n")

      config_file.close()

  

  

- def check_blockers(bzurl):

-     bzinterface = BlockerBugs(url=bzurl)

+ def check_blockers():

+     bzinterface = BlockerBugs()

      active_milestones = Milestone.query.filter_by(active=True).all()

      for milestone in active_milestones:

          missing_bugs = []
@@ -185,21 +183,18 @@ 

      # Related: https://pagure.io/fedora-qa/blockerbugs/issue/184

      fullsync = True

  

-     # grab the URL from app config so that it's syncing from the right place

-     bzurl = app.config['BUGZILLA_XMLRPC']

- 

      current_milestone = Milestone.query.filter_by(active=True).first()

      if not current_milestone:

          sys.stderr.write('BugSync ERROR: No active releases found!')

          sys.exit(1)

  

-     sync = BugSync(db, url=bzurl)

+     sync = BugSync(db)

  

      sync.update_active(full_sync=fullsync)

  

      if docheck:

          print("Checking bug sync status")

-         check_blockers(bzurl)

+         check_blockers()

  

  

  def sync_updates(args):

file modified
+8 -8
@@ -32,15 +32,16 @@ 

      SECRET_KEY = None  # REPLACE THIS WITH A RANDOM STRING

      SQLALCHEMY_DATABASE_URI = 'sqlite://'  # just 'sqlite://' without a path means an in-memory db

      SQLALCHEMY_TRACK_MODIFICATIONS = False

-     BUGZILLA_URL = 'https://bugzilla.stage.redhat.com/'

-     BUGZILLA_XMLRPC = BUGZILLA_URL + 'xmlrpc.cgi'

+     BUGZILLA_URL = 'https://bugzilla.stage.redhat.com'

+     BUGZILLA_API_KEY = ''

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

      FAS_ENABLED = False

      """When FAS is not enabled, a fake stub is used instead, which allows the user to log in (under

      the credentials+groups defined here in this config) without authentication."""

      FAS_ADMIN_GROUP = 'qa-admin'

-     FAS_USER = ''

-     FAS_PASSWORD = ''

+     FAS_USER = 'developer'

+     """This is mostly useful for developers, when they want to simulate a login for a certain

+     user."""

      FAS_HTTPS_REQUIRED = False

      FAS_CHECK_CERT = False

      FAS_BASE_URL = 'https://admin.stg.fedoraproject.org/accounts/'
@@ -82,8 +83,7 @@ 

      """A `Config` subclass intended for a production deployment"""

      PRODUCTION = True

      DEBUG = False

-     BUGZILLA_URL = 'https://bugzilla.redhat.com/'

-     BUGZILLA_XMLRPC = BUGZILLA_URL + 'xmlrpc.cgi'

+     BUGZILLA_URL = 'https://bugzilla.redhat.com'

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

      FAS_ENABLED = True

      FAS_HTTPS_REQUIRED = True
@@ -124,9 +124,9 @@ 

          sys.exit(1)

  

      # And then try to get more data from OpenShift env

-     additional_env_keys = ["FAS_PASSWORD", "FAS_USER", "FAS_ADMIN_GROUP", "PAGURE_REPO_TOKEN", "PAGURE_REPO_WEBHOOK_KEY",

+     additional_env_keys = ["FAS_ADMIN_GROUP", "PAGURE_REPO_TOKEN", "PAGURE_REPO_WEBHOOK_KEY",

                             "PAGURE_REPO", "PAGURE_BOT_USERNAME", "PAGURE_BOT_ENABLED", "PAGURE_URL", "PAGURE_API",

-                            "BUGZILLA_URL", "BUGZILLA_XMLRPC", "BODHI_URL", "BLOCKERBUGS_URL", "BLOCKERBUGS_API",

+                            "BUGZILLA_URL", "BUGZILLA_API_KEY", "BODHI_URL", "BLOCKERBUGS_URL", "BLOCKERBUGS_API",

                             "SECRET_KEY", "FAS_BASE_URL"]

      missing_data = False

  

file modified
+18 -24
@@ -28,8 +28,7 @@ 

  import itertools

  

  from blockerbugs import app, db, __version__

- from blockerbugs.util.bz_interface import BlockerProposal, BZInterfaceError

- from blockerbugs.util import pagure_bot

+ from blockerbugs.util import bz_interface, pagure_bot

  from blockerbugs.models.bug import Bug

  from blockerbugs.models.milestone import Milestone

  from blockerbugs.models.update import Update
@@ -403,23 +402,17 @@ 

  

  def get_bugzilla():

      # create bz interface object using the app's config settings

-     if not app.testing:

-         if app.config.get('FAS_USER'):

- 

-             app.logger.debug('logging into bugzilla (%s) as %s' % (app.config['BUGZILLA_XMLRPC'], app.config['FAS_USER']))

- 

-             return bugzilla.RHBugzilla4(url=app.config['BUGZILLA_XMLRPC'],

-                                         user=app.config['FAS_USER'],

-                                         password=app.config['FAS_PASSWORD'],

-                                         cookiefile=None,

-                                         tokenfile=None)

-         else:

-             app.logger.warning('No FAS_USER configured. bugzilla modifications will NOT work!')

-             return bugzilla.RHBugzilla4(url=app.config['BUGZILLA_XMLRPC'], cookiefile=None, tokenfile=None)

-     else:

+     if app.testing:

          app.logger.error('Bugzilla connections with test configuration is not supported!')

          return None

  

+     if app.config.get('BUGZILLA_API_KEY'):

+         app.logger.debug('logging into bugzilla (%s) with an api key', app.config['BUGZILLA_URL'])

+     else:

+         app.logger.warning('No BUGZILLA_API_KEY configured. bugzilla modifications will NOT work!')

+ 

+     return bz_interface.create_bugzilla()

+ 

  

  @main.route('/fas_bugzilla', methods=['GET', 'POST'])

  @fas_login_required
@@ -429,10 +422,11 @@ 

      if fas_bz_form.validate_on_submit():

          try:

              # try logging into bz if user/pass are valid

-             bz = bugzilla.RHBugzilla4(url=app.config['BUGZILLA_XMLRPC'],

-                                         user=fas_bz_form.bz_user.data,

-                                         password=fas_bz_form.bz_pass.data,

-                                         cookiefile=None, tokenfile=None)

+             bz = bugzilla.RHBugzilla4(url=app.config['BUGZILLA_URL'],

+                                       user=fas_bz_form.bz_user.data,

+                                       password=fas_bz_form.bz_pass.data,

+                                       use_creds=False,

+                                       force_xmlrpc=True)

              bz.disconnect()

  

              #create new user_info if user with given FAS-login does not exist
@@ -488,13 +482,13 @@ 

          # The form does basic datatype validation but we need to check for valid

          # data

          bz = get_bugzilla()

-         proposer = BlockerProposal(bz, bugid, trackers, bugform.blocker.data,

-                                    bugform.freeze_exception.data, user_info.bz_user)

+         proposer = bz_interface.BlockerProposal(bz, bugid, trackers, bugform.blocker.data,

+                                                 bugform.freeze_exception.data, user_info.bz_user)

  

          # check bug to make sure it exists and that it isn't closed

          try:

              proposer.check_proposed_bug()

-         except BZInterfaceError as e:

+         except bz_interface.BZInterfaceError as e:

              bugform.bugid.errors = [e.msg]

  

          # check to make sure that any proposals aren't being re-done
@@ -516,7 +510,7 @@ 

                  return render_template('thanks.html', bugid=bugid,

                                         isblocker=bugform.blocker.data,

                                         isfe=bugform.freeze_exception.data)

-             except BZInterfaceError as e:

+             except bz_interface.BZInterfaceError as e:

                  bugform.bugid.errors = [e.msg]

                  app.logger.info('bug proposal form errors: %s' % e.msg)

          else:

@@ -29,7 +29,7 @@ 

              External links:

              <td><a href='https://koji.fedoraproject.org/koji/search?type=package&match=glob&terms={{ packagename }}' title="Koji Builds" target="_blank" rel="noopener"><img alt="Koji" src="{{ url_for('static', filename='img/koji_icon.png') }}"></a></td>

              <td><a href='https://src.fedoraproject.org/rpms/{{ packagename }}' title="Package Source" target="_blank" rel="noopener"><img alt="Package Source" src="{{ url_for('static', filename='img/pkgsrc_icon.png') }}"></a></td>

-             <td><a href='{{ bz_url }}buglist.cgi?bug_status=__open__&classification=Fedora&product=Fedora&product=Fedora%20EPEL&component={{ packagename }}' title="Component Bugs" target="_blank" rel="noopener"> <img alt="Component Bugs" src="{{ url_for('static', filename='img/bugzilla_icon.png') }}"></a></td>

+             <td><a href='{{ bz_url }}/buglist.cgi?bug_status=__open__&classification=Fedora&product=Fedora&product=Fedora%20EPEL&component={{ packagename }}' title="Component Bugs" target="_blank" rel="noopener"> <img alt="Component Bugs" src="{{ url_for('static', filename='img/bugzilla_icon.png') }}"></a></td>

          </div>

      </div>

  </div>

@@ -1,4 +1,4 @@ 

  <b>Depends on the following bugs:</b><br>

  {% for dep in bug.depends_on %}

- <a href="{{ bz_url }}show_bug.cgi?id={{ dep }}" target="_blank" rel="noopener">{{ dep }}</a><br>

+ <a href="{{ bz_url }}/show_bug.cgi?id={{ dep }}" target="_blank" rel="noopener">{{ dep }}</a><br>

  {% endfor %}

@@ -32,8 +32,8 @@ 

              </thead>

              <tbody>

                  <tr>

-                     <td><a href="{{ bz_url }}show_bug.cgi?id={{ info['blocker_tracker'] }}"> {{ info['blocker_tracker'] }}</a></td>

-                     <td><a href="{{ bz_url }}show_bug.cgi?id={{ info['fe_tracker'] }}"> {{ info['fe_tracker'] }}</a></td>

+                     <td><a href="{{ bz_url }}/show_bug.cgi?id={{ info['blocker_tracker'] }}"> {{ info['blocker_tracker'] }}</a></td>

+                     <td><a href="{{ bz_url }}/show_bug.cgi?id={{ info['fe_tracker'] }}"> {{ info['fe_tracker'] }}</a></td>

                  </tr>

              </tbody>

          </table>

file modified
+2 -4
@@ -34,13 +34,11 @@ 

      """Main class for syncing existing and retrieving new blocker (and other) bugs"""

  

      def __init__(self, db: flask_sqlalchemy.SQLAlchemy,

-                  bzinterface: Optional[bz_interface.BlockerBugs] = None, url: Optional[str] = None

-                  ) -> None:

-         """:param url: Bugzilla API url"""

+                  bzinterface: Optional[bz_interface.BlockerBugs] = None) -> None:

          self.db: flask_sqlalchemy.SQLAlchemy = db

          self.log: logging.Logger = logging.getLogger('bug_sync')

          self.bzinterface: bz_interface.BlockerBugs = (bzinterface or

-                                                       bz_interface.BlockerBugs(url=url))

+                                                       bz_interface.BlockerBugs())

  

      def extract_information(self, bug: bzBug, tracker_type: str) -> dict[str, Any]:

          """Create a dict with extracted Bug information. See the code to learn the dict keyvals.

@@ -21,6 +21,7 @@ 

  

  import logging

  import datetime

+ import urllib.parse

  from typing import Optional, Any

  

  import bugzilla
@@ -39,6 +40,34 @@ 

                'limit': BUGZILLA_QUERY_LIMIT}

  

  

+ def create_bugzilla(url: Optional[str] = None,

+                     api_key: Optional[str] = None) -> bugzilla.Bugzilla:

+     """Create a `bugzilla.Bugzilla` instance. If function arguments are left empty, the values are

+     loaded from the config file. If api_key is used (i.e. the query is not anonymous), Bugzilla is

+     checked whether the key was accepted (otherwise an exception is raised).

+     """

+     # FIXME: xmlrpc must be used until https://pagure.io/fedora-qa/blockerbugs/issue/184 is resolved

+     bz = bugzilla.Bugzilla(url=url or app.config['BUGZILLA_URL'],

+                            api_key=api_key or app.config['BUGZILLA_API_KEY'],

+                            use_creds=False,

+                            force_xmlrpc=True)

+     if bz.api_key:

+         # Verify successful authentication. This immediately raises an exception if auth fails.

+         ok = bz.logged_in

+         assert ok is True

+ 

+     return bz

+ 

+ 

+ def get_bugzilla_url(bz: bugzilla.Bugzilla) -> str:

+     """Get main bugzilla url (e.g. 'https://bugzilla.redhat.com') from an existing

+     `bugzilla.Bugzilla` instance.

+     """

+     parsed_uri = urllib.parse.urlparse(bz.url)

+     url = '{uri.scheme}://{uri.netloc}'.format(uri=parsed_uri)

+     return url

+ 

+ 

  class BZInterfaceError(Exception):

      """A custom wrapper for XMLRPC errors from Bugzilla"""

  
@@ -52,31 +81,14 @@ 

  class BlockerBugs():

      """The main class for querying Bugzilla"""

  

-     def __init__(self, user: Optional[str] = None, password: Optional[str] = None,

-                  url: Optional[str] = 'https://bugzilla.redhat.com/xmlrpc.cgi',

-                  bz: Optional[bugzilla.Bugzilla] = None,

+     def __init__(self, bz: Optional[bugzilla.Bugzilla] = None,

                   logger: Optional[logging.Logger] = None) -> None:

-         """:param user: Username to log in as. Use `None` for anonymous access.

-            :param password: User password. Use `None` for anonymous access.

-            :param url: Bugzilla API url.

-            :param bz: `Bugzilla` instance. Created automatically if not provided. If provided,

-                       the `user`, `password` and `url` values are ignored.

+         """:param bz: `bugzilla.Bugzilla` instance. Created automatically if not provided.

             :param logger: A custom `Logger` instance. Otherwise a default Logger is created.

          """

          self.logger = logger or logging.getLogger('bz_interface')

-         self.bz: bugzilla.Bugzilla = bz

-         if not bz:

-             if not (user and password):

-                 self.bz = bugzilla.Bugzilla(url=url,

-                                             cookiefile=None,

-                                             tokenfile=None)

-             else:

-                 self.bz = bugzilla.Bugzilla(url=url,

-                                             user=user,

-                                             password=password,

-                                             cookiefile=None,

-                                             tokenfile=None)

-         self.logger.info('Using bugzilla URL: %s' % url)

+         self.bz: bugzilla.Bugzilla = bz or create_bugzilla()

+         self.logger.info('Using bugzilla URL: %s', get_bugzilla_url(self.bz))

  

      # https://bugzilla.stage.redhat.com/buglist.cgi?bug_status=NEW&bug_status=ASSIGNED

      # &bug_status=POST&bug_status=MODIFIED&classification=Fedora&component=anaconda&f1=component
@@ -88,7 +100,7 @@ 

  

          :param last_update: If provided, the query is modified to ask only about bugs which have

                              recent modifications; otherwise asks about all bugs.

-         :offset: offset to the query instead of just getting the first N results

+         :param offset: offset to the query instead of just getting the first N results

          :returns: a dict which can be fed into `bugzilla.Bugzilla.query()`.

          """

          query = {}
@@ -171,9 +183,9 @@ 

          # https://bugzilla.redhat.com/buglist.cgi?bug_status=__open__&

          # f1=flagtypes.name&o1=substring&query_format=advanced&v1=fedora_prioritized_bug%2B

          query = self.bz.url_to_query(

-             "{}buglist.cgi?bug_status=__open__&f1=flagtypes.name&o1=substring"

+             "{}/buglist.cgi?bug_status=__open__&f1=flagtypes.name&o1=substring"

              "&query_format=advanced&v1=fedora_prioritized_bug%2B".format(

-                 app.config['BUGZILLA_URL']))

+                 get_bugzilla_url(self.bz)))

          buglist = self.bz.query(query)

          return buglist

  

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

      puts it into the admin group (as defined in the app config).

      """

      fake_user = munch.Munch(

-         username=app.config['FAS_USER'] or 'developer',

+         username=app.config['FAS_USER'],

          groups=[app.config['FAS_ADMIN_GROUP']])

  

      def __init__(self, app):

file modified
+2 -4
@@ -4,10 +4,8 @@ 

  SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://dbuser:dbpassword@dbhost:dbport/dbname'

  FAS_ENABLED = False

  FAS_ADMIN_GROUP = "magic-fas-group"

- FAS_USER = "magicuser@unicornsarereal.com"

- FAS_PASSWORD = "password"

- BUGZILLA_URL = 'https://bugzilla.stage.redhat.com/'

- BUGZILLA_XMLRPC = 'https://bugzilla.stage.redhat.com/xmlrpc.cgi'

+ BUGZILLA_URL = 'https://bugzilla.stage.redhat.com'

+ BUGZILLA_API_KEY = ''  # INSERT YOUR SECRET API KEY FROM BUGZILLA USER SETTINGS

  BODHI_URL = 'https://admin.stg.fedoraproject.org/updates/'

  FILE_LOGGING = False

  LOGFILE = '/var/log/blockerbugs/blockerbugs.log'

This adds BUGZILLA_API_KEY into config, which is then used for bugzilla
authentication in each call. This replaces FAS_USER + FAS_PASSWORD (formerly
used only for proposing blockers).

This change is mandated by a recent authentication change in Bugzilla:
https://listman.redhat.com/archives/bugzilla-announce-list/2022-February/msg00000.html

This patch fixes the actual bug proposal submission process, but doesn't fix
verifying bugzilla accounts for users, yet.

Also, BUGZILLA_XMLRPC config option got dropped, because it's not needed for
python-bugzilla, we can feed it with BUGZILLA_URL.

Related: https://pagure.io/fedora-qa/blockerbugs/issue/231


This is still somewhat WIP. Reviews welcome, though.

Build succeeded.

It seems to work solidly. Anyone wants to review this? No big deal if you don't, I think the changes are pretty safe.

Looks good to me👌👍 , thanks

Pull-Request has been closed by kparal

4 months ago