[Asterisk-code-review] digium_jira: allow for greater flexibility in retrieving a JIRA client (repotools[master])

George Joseph asteriskteam at digium.com
Wed Mar 30 06:41:21 CDT 2022


George Joseph has submitted this change. ( https://gerrit.asterisk.org/c/repotools/+/18252 )

Change subject: digium_jira: allow for greater flexibility in retrieving a JIRA client
......................................................................

digium_jira: allow for greater flexibility in retrieving a JIRA client

Previously retrieving a JIRA client relied on hard-coded values, and
really only one server could be connected to. Getting authorization was
also limited to reading it from a a file (.jira_login) that contained
only the username and password, or the command line. Also clients were
not cached so subsequent calls to get the client meant renegotiating
the connection.

This patch maintains the old way of getting authorization, but adds a
new way to configure settings and authorization for a JIRA client. If
specified configuration is read from a .jira_config.yaml file. Client
configuration is looked up by a given key name, and options and auth
are specified as sub-structures.

Clients are also now cached, so subsequent calls for a named (note
'None' is allowed as a valid name) connection returned the already
established connection.

Change-Id: I5b4d439259434f576b98c39d4a49ff7414a6f17d
---
M digium_jira.py
1 file changed, 185 insertions(+), 21 deletions(-)

Approvals:
  Joshua Colp: Looks good to me, but someone else must approve
  George Joseph: Looks good to me, approved; Approved for Submit



diff --git a/digium_jira.py b/digium_jira.py
index 31aa301..2a0e64a 100644
--- a/digium_jira.py
+++ b/digium_jira.py
@@ -8,6 +8,7 @@
 import logging
 import os
 import getpass
+import yaml
 
 from jira.client import JIRA
 from version_parser import AsteriskVersion
@@ -17,37 +18,200 @@
 
 DEFAULT_PROJECT = 'asterisk'
 
-def _get_jira_auth():
-    """Get JIRA credentials"""
+DEFAULT_CLIENT_CONFIG = {
+    "options": {
+        "server": "https://issues.asterisk.org/jira/",
+    },
+}
+
+def _get_file(filename):
+    """Retrieve a file by filename
+
+    Opens, and returns the first file found of the given name checking in the
+    following directory order:
+
+        1) current working directory
+        2) current user's home directory
+
+    Keyword Arguments:
+        filename - the name of the file to open
+    """
+
+    paths = [
+        ".",
+        os.path.expanduser("~"),
+    ]
+
+    for p in paths:
+        try:
+            f = open("{0}/{1}".format(p, filename), "r")
+            return f
+        except:
+            pass
+
+    raise IOError("File '{0}' not found in paths '{1}'".format(
+        filename, ", ".join(paths)))
+
+
+def _auth_from_file(config):
+    """Get JIRA authentication information from a '.jira_login' file
+
+    A '.jira_login' file contains authentication information only, and should
+    contain only two lines. The first line should be the username and the
+    second line the password, e.g.
+
+      <username>
+      <password>
+
+    Leading and trailing spaces are stripped.
+
+    Keyword Arguments:
+        config - a working configuration
+    """
 
     try:
-        with open(os.path.expanduser('~') + "/.jira_login", "r") as jira_cache:
-            jira_user = jira_cache.readline().strip()
-            jira_pw = jira_cache.readline().strip()
-            return (jira_user, jira_pw)
-    except IOError:
-        pass
+        with _get_file(".jira_login") as f:
+            username = f.readline().strip()
+            if not username:
+                return False
 
-    # Didn't get auth details from file, try interactive instead.
-    print "Please enter your username and pw for JIRA."
-    jira_user = raw_input("Username: ")
-    jira_pw = getpass.getpass("Password: ")
+            password = f.readline().strip()
+            if not password:
+                LOGGER.warn("Password not specified for {0} in {1}".format(
+                    username, f.name))
 
-    return (jira_user, jira_pw)
+            config.update({
+                "basic_auth": (username, password),
+            })
+    except:
+        LOGGER.debug(".jira_login not found")
+        return False
+
+    return True
 
 
-def get_jira_client():
-    """Return a connected JIRA client"""
+def _auth_from_input(config):
+    """Get JIRA authentication information from interactive user input
 
-    jira_user, jira_password = _get_jira_auth()
+    Keyword Arguments:
+        config - a working configuration
+    """
 
-    jira_options = {
-        'server': 'https://issues.asterisk.org/jira/'
-    }
+    print "Please enter your username and password for JIRA."
 
-    jira = JIRA(options=jira_options, basic_auth=(jira_user, jira_password))
+    config.update({
+        "basic_auth": (
+            raw_input("Username: "),
+            getpass.getpass("Password: ")
+        ),
+    })
 
-    return jira
+    return True
+
+
+def _config_from_file(name, config):
+    """Get JIRA configuration (connection and authentication) information from
+    a '.jira_config.yaml' file
+
+    Retrieve the appropriate client configuration based on the given name. A
+    client section should be in the following YAML based format:
+
+      <client name>:
+        options: <jira client options dictionary>
+        <auth type> : <auth type options>
+
+    For more information on the JIRA client and authentication options see:
+
+      https://jira.readthedocs.io/
+
+    Keyword Arguments:
+        name - a client section name
+        config - a working configuration
+    """
+
+    try:
+        with _get_file(".jira_config.yaml") as f:
+            c = yaml.safe_load(f)
+
+            section = c.get(name)
+            if section:
+                config.update(c[name])
+            elif name:
+                # If a name was specified and not found then don't try getting
+                # config/auth using other ways, but force a connection failure
+                config["options"]["server"] = "None"
+
+    except yaml.YAMLError as e:
+        LOGGER.error(e)
+        return False
+    except:
+        LOGGER.debug(".jira_config not found")
+        return False
+
+    return True
+
+
+def _get_jira_config(config, name=None):
+    """Get JIRA client configuration and/or login credentials
+
+    There are currently three different ways configuration (connection and
+    authentication) can be retrieved (in order of attempted retrieval):
+
+    1) From a ~/.jira_config.yaml file (see _config_from_file)
+    2) From a ~/.jira_login file (see _auth_from_file)
+    3) From interactive user input (see _auth_from_input)
+
+    Keyword Arguments:
+        name - a client name used to look up configuration
+        config - current working configuration
+    """
+
+    return _config_from_file(name, config) or _auth_from_file(config) \
+        or _auth_from_input(config)
+
+
+# Typically it is advised to not default a parameter with a dictionary as it
+# can lead to unexpected side effects, but those are the exact effects we want
+# with a cache. By initializing the cache this way a caller can supply their
+# own cache if they so desire, and if not then python takes care of keeping the
+# internal cache between calls.
+def get_jira_client(name=None, config=None, cache={}):
+    """Return a connected JIRA client
+
+    This stores connected clients by 'name' in 'cache'. Subsequent calls using
+    the same name will return the cached client object.
+
+    Keyword Arguments:
+        name - The client name used for lookup
+        config - JIRA client configuration
+        cache - Stores connected clients by name
+    """
+
+    # If the client already exists in the cache then use that one
+    client = cache.get(name)
+    if client:
+        return client
+
+    # Use defaults for any unspecified configuration options
+    working_config = DEFAULT_CLIENT_CONFIG.copy()
+    if config:
+        working_config.update(config)
+
+    success = _get_jira_config(working_config, name)
+    if success:
+        try:
+            LOGGER.debug("Attempting to connect to JIRA server '{0}".format(
+                working_config["options"]["server"]))
+            client = JIRA(**working_config)
+            cache[name] = client
+        except:
+            success = False
+
+    if not success:
+        LOGGER.error("Unable to connect to '{0}'".format(
+            working_config["options"]["server"]))
+
+    return client
 
 
 class DigiumJira(object):

-- 
To view, visit https://gerrit.asterisk.org/c/repotools/+/18252
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: repotools
Gerrit-Branch: master
Gerrit-Change-Id: I5b4d439259434f576b98c39d4a49ff7414a6f17d
Gerrit-Change-Number: 18252
Gerrit-PatchSet: 1
Gerrit-Owner: Kevin Harwell <kharwell at digium.com>
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at sangoma.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20220330/16bf318c/attachment-0001.html>


More information about the asterisk-code-review mailing list