#58 downstream: Add ability to use different jira instances
Merged 5 years ago by ralph. Opened 5 years ago by dustymabe.
dustymabe/sync-to-jira dusty  into  develop

file modified
+49 -22
@@ -24,15 +24,40 @@ 

  import arrow

  import jira.client

  

+ from sync2jira.intermediary import Issue

+ 

  log = logging.getLogger(__name__)

  

  remote_link_title = "Upstream issue"

  

  jira_cache = {}

  

+ def _get_jira_client(issue, config):

+ 

+     # The name of the jira instance to use is stored under the 'map'

+     # key in the config where each upstream is mapped to jira projects.

+     # It is conveniently added to the Issue object from intermediary.py

+     # so we can use it here:

+ 

+     if not isinstance(issue, Issue):

+         log.error("passed in issue is not an Issue instance")

+         log.error("It is a %s" % (type(issue).__name__))

+         raise

+ 

+     # Use the Jira instance set in the issue config. If none then

+     # use the configured default jira instance.

+     jira_instance = issue.downstream.get('jira_instance', False)

+     if not jira_instance:

+         jira_instance = config['sync2jira'].get('default_jira_instance', False)

+     if not jira_instance:

+         log.error("No jira_instance for issue and there is no default in the config")

+         raise

  

- def _matching_jira_issue_query(issue, config, free=False):

-     client = jira.client.JIRA(**config['sync2jira']['jira'])

+ 

+     client = jira.client.JIRA(**config['sync2jira']['jira'][jira_instance])

+     return client

+ 

+ def _matching_jira_issue_query(client, issue, config, free=False):

      query = 'issueFunction in linkedIssuesOfRemote("%s") and ' \

          'issueFunction in linkedIssuesOfRemote("%s")' % (

              remote_link_title, issue.url)
@@ -43,19 +68,19 @@ 

      return results

  

  

- def get_existing_jira_issue(issue, config):

+ def _get_existing_jira_issue(client, issue, config):

      """ Get a jira issue by the linked remote issue.

  

      This is the new supported way of doing this.

      """

-     results = _matching_jira_issue_query(issue, config)

+     results = _matching_jira_issue_query(client, issue, config)

      if results:

          return results[0]

      else:

          return None

  

  

- def get_existing_jira_issue_legacy(issue, config):

+ def _get_existing_jira_issue_legacy(client, issue, config):

      """ This is our old way of matching issues: use the special url field.

  

      This will be phased out and removed in a future release.
@@ -65,7 +90,6 @@ 

      kwargs["External issue URL"] = "%s" % issue.url

      kwargs = sorted(kwargs.items(), key=operator.itemgetter(0))

  

-     client = jira.client.JIRA(**config['sync2jira']['jira'])

      query = " AND ".join([

          "=".join(["'%s'" % k, "'%s'" % v]) for k, v in kwargs

          if v is not None
@@ -77,10 +101,9 @@ 

          return None

  

  

- def _attach_link(config, downstream, remote_link):

+ def _attach_link(client, downstream, remote_link):

      log.info("    Attaching tracking link %r to %r", remote_link, downstream.key)

      modified_desc = downstream.fields.description + " "

-     client = jira.client.JIRA(**config['sync2jira']['jira'])

  

      # This is crazy.  Querying for application links requires admin perms which

      # we don't have, so duckpunch the client to think it has already made the
@@ -99,7 +122,7 @@ 

      return downstream

  

  

- def upgrade_jira_issue(downstream, issue, config):

+ def _upgrade_jira_issue(client, downstream, issue, config):

      """ Given an old legacy-style downstream issue...

      ...upgrade it to a new-style issue.

  
@@ -112,16 +135,15 @@ 

  

      # Do it!

      remote_link = dict(url=issue.url, title=remote_link_title)

-     _attach_link(config, downstream, remote_link)

+     _attach_link(client, downstream, remote_link)

  

  

- def create_jira_issue(issue, config):

+ def _create_jira_issue(client, issue, config):

      log.info("    Creating %r issue for %r", issue.downstream, issue)

      if config['sync2jira']['testing']:

          log.info("      Testing flag is true.  Skipping actual creation.")

          return

  

-     client = jira.client.JIRA(**config['sync2jira']['jira'])

      kwargs = dict(

          summary=issue.title,

          description=issue.url,
@@ -142,13 +164,12 @@ 

      downstream = client.create_issue(**kwargs)

  

      remote_link = dict(url=issue.url, title=remote_link_title)

-     _attach_link(config, downstream, remote_link)

+     _attach_link(client, downstream, remote_link)

      return downstream

  

  

- def close_as_duplicate(duplicate, keeper, config):

+ def _close_as_duplicate(client, duplicate, keeper, config):

      log.info("  Closing %s as duplicate of %s", duplicate.permalink(), keeper.permalink())

-     client = jira.client.JIRA(**config['sync2jira']['jira'])

      if config['sync2jira']['testing']:

          log.info("      Testing flag is true.  Skipping actual delete.")

          return
@@ -183,8 +204,11 @@ 

  

  

  def close_duplicates(issue, config):

+     # Create a client connection for this issue

+     client = _get_jira_client(issue, config)

+ 

      log.info("Looking for dupes of upstream %s, %s", issue.url, issue.title)

-     results = _matching_jira_issue_query(issue, config, free=True)

+     results = _matching_jira_issue_query(client, issue, config, free=True)

      if len(results) <= 1:

          log.info("  No duplicates found.")

          return
@@ -192,16 +216,19 @@ 

      results = sorted(results, key=lambda x: arrow.get(x.fields.created))

      keeper, duplicates = results[0], results[1:]

      for duplicate in duplicates:

-         close_as_duplicate(duplicate, keeper, config)

+         _close_as_duplicate(client, duplicate, keeper, config)

  

  

  def sync_with_jira(issue, config):

      log.info("Considering upstream %s, %s", issue.url, issue.title)

  

+     # Create a client connection for this issue

+     client = _get_jira_client(issue, config)

+ 

      # First, check to see if we have a matching issue using the new method.

      # If we do, then just bail out.  No sync needed.

      log.info("  Looking for matching downstream issue via new method.")

-     existing = get_existing_jira_issue(issue, config)

+     existing = _get_existing_jira_issue(client, issue, config)

      if existing:

          log.info("   Found existing, matching downstream %r.", existing.key)

          return
@@ -210,7 +237,7 @@ 

      # is nothing left to do than to but to create the issue and return.

      if not config['sync2jira'].get('legacy_matching', True):

          log.debug("   Legacy matching disabled.")

-         create_jira_issue(issue, config)

+         _create_jira_issue(client, issue, config)

          return

  

      # Otherwise, if we *are* configured to do legacy matching, then try and
@@ -218,8 +245,8 @@ 

      # - If we can't find it, create it.

      # - If we can find it, upgrade it to the new method.

      log.info("  Looking for matching downstream issue via legacy method.")

-     match = get_existing_jira_issue_legacy(issue, config)

+     match = _get_existing_jira_issue_legacy(client, issue, config)

      if not match:

-         create_jira_issue(issue, config)

+         _create_jira_issue(client, issue, config)

      else:

-         upgrade_jira_issue(match, issue, config)

+         _upgrade_jira_issue(client, match, issue, config)

file modified
+6 -5
@@ -4,6 +4,7 @@ 

  import unittest

  

  import sync2jira.downstream as d

+ import jira.client

  

  

  class TestDownstream(unittest.TestCase):
@@ -25,7 +26,7 @@ 

          # Ensure that we get results back from the jira client.

          target1 = "target1"

          client.return_value.search_issues = mock.MagicMock(return_value=[target1])

-         result = d.get_existing_jira_issue_legacy(issue, config)

+         result = d._get_existing_jira_issue_legacy(jira.client.JIRA(), issue, config)

          eq_(result, target1)

  

          client.return_value.search_issues.assert_called_once_with(
@@ -45,7 +46,7 @@ 

          target1 = "target1"

          issue = MockIssue()

          client.return_value.search_issues = mock.MagicMock(return_value=[target1])

-         result = d.get_existing_jira_issue(issue, config)

+         result = d._get_existing_jira_issue(jira.client.JIRA(), issue, config)

          eq_(result, target1)

  

          client.return_value.search_issues.assert_called_once_with(
@@ -65,7 +66,7 @@ 

          issue = MockIssue()

          client_obj = mock.MagicMock()

          client.return_value = client_obj

-         d.upgrade_jira_issue(downstream, issue, config)

+         d._upgrade_jira_issue(jira.client.JIRA(), downstream, issue, config)

          remote = {

              'url': 'http://threebean.org',

              'title': 'Upstream issue',
@@ -91,7 +92,7 @@ 

              url = 'http://threebean.org'

  

          config['sync2jira']['testing'] = False

-         result = d.create_jira_issue(MockIssue(), config)

+         result = d._create_jira_issue(jira.client.JIRA(), MockIssue(), config)

          eq_(result, target1)

          client.return_value.create_issue.assert_called_with(

              components=[{'name': 'alsoawesome'}],
@@ -119,7 +120,7 @@ 

              url = 'http://threebean.org'

  

          config['sync2jira']['testing'] = False

-         result = d.create_jira_issue(MockIssue(), config)

+         result = d._create_jira_issue(jira.client.JIRA(), MockIssue(), config)

          eq_(result, target1)

          client.return_value.create_issue.assert_called_with(

              components=[{'name': 'alsoawesome'}],

There are a few changes in this patch set. First we'd like to
support different github/pagure repos being able to be synced
with different instances of JIRA. We currently have two within
Red Hat that we are using, but this strategy should alos support
more than two. Summary of changes in this patch:

  • add support for multiple jira instances to the config under the
    config['sync2jira']['jira'] config location. For example if I
    wanted to grab the auth info for the pnt-jira I would access
    config['sync2jira']['jira']['pnt-jira'].
  • add support for a 'default_jira_instance' config key that allows
    for a user to specify which jira in the jira dict to use as a default
    in case config entries don't specify a jira instance to use.
  • switch function names from functions that that are only called in
    this file to have a preceding underscore (func -> _func)
  • make the only functions that get called from outside this file to
    be the ones that crete a client connection to the jira instance
  • pass the jira client object around to the private functions

rebased onto cf7255e

5 years ago

a few comments:

  • i'm still testing this out so some fixes may still come in
  • if we merge/release this we will need to coordinate config changes
  • could use some help making sure I did the tests right.. seems a little hacky right now

No major objections here. It looks good. I just want to try it out before merging. I can handle the config rollout when the time comes.

are there any docs that need updating?

are there any docs that need updating?

Nope, it's all self-contained here.

Pull-Request has been merged by ralph

5 years ago