Thursday, April 26, 2012

Greenhopper, Jira, and REST

One of the somewhat frustrating problems I'm dealing with in Greenhopper is that I want the ability to treat a linked issue like a subtask, but without all the restrictions of a subtask.  Subtasks have at least three limitations that get in my way:
  1. They must be in the same project as their parent
  2. They must have the same permissions (issue-level security) as their parent
  3. They must be of an issue type that is flagged as a "subtask" type, so for example, a "Feature" cannot be a subtask of a "Story" unless you create a separate "Feature (subtask)" issues type.
Issue #1 is probably the most frustrating, because product management and the exec team at a software company think in terms of high-level features or use cases for which the implementation will often cross project boundaries.  (An example at Eucalyptus is that a new use case may require changes to both Eucalyptus and Euca2ools.)

The Greenhopper UI operates mostly via a REST API, and so far this API is not well documented.  Last night I got around this lack of documentation by using mitmproxy to monitor calls while moving issues up and down the planning page in Greenhopper's Rapid Board.  Then I added a simple rest client class to jiranemo based on restkit.  I made two helper functions: one to get the rest representation of an issue, and another to change the rank of an issue in Greenhopper.   My script looks like this:


import sys
import pyjira
from jiranemo import jiracfg

# Set the exception hook to enter a debugger on
# uncaught exceptions
from jiranemo.lib import util
sys.excepthook = util.genExcepthook(debug=True,

# Read ${HOME}/.jirarc, and set up clients and auth caches.
cfg = jiracfg.JiraConfiguration(readConfigFiles=True)
authorizer = pyjira.auth.CachingInteractiveAuthorizer(cfg.authCache)
ccAuthorizer = pyjira.auth.CookieCachingInteractiveAuthorizer(cfg.cookieCache)
client = pyjira.JiraClient(cfg.wsdl, 
                           (cfg.user, cfg.password), 

# Do a simple JQL query via the SOAP client, return 20 results
issues = client.client.getIssuesFromJqlSearch(
    '''project = "system testing 2" order by Rank DESC''', 20)
for x in issues:
    # Get the REST representation of each issue, because links
    # aren't shown in the SOAP representation
    rest_issue = client.restclient.get_issue(x.key)

    for link in rest_issue['fields']['issuelinks']:
      if link['type'].has_key('inward') and \
         link['type']['inward'] == "is blocked by":
        # Rank the linked issue above this one in Greenhopper
        result = client.restclient.gh_rank(link['inwardIssue']['key'], 
The code could use some error checking, but this is a pretty simple starting point for doing something that Jira and Greenhopper can't do on their own.

Wednesday, April 25, 2012

Resurrecting Jiranemo

About six years ago, David Christian developed a JIRA CLI called jiranemo (his original blog post is, somewhat surprisingly, still around on the rPath website).  After he left rPath, I spent some time updating the code for Jira 4 and adding some minor features, but it's been mostly stagnant for about two years.  In the meantime, Jira 5 has been released, and the core dependency of jiranemo, SOAPpy, has been declared dead.

This month, Eucalyptus started on the migration path  from using a combination of RT and Launchpad to using Jira.  I'm really excited about the change, and it gave me a chance to pick up the jiranemo code again.  I've now converted it from SOAPpy to suds, and on Monday I used it to import 2000 issues from RT into jira (stay tuned for details on that becoming a publicly-accessible system).  I had database access to RT, but all of the interaction with jira was done through the SOAP API.  ( I realize they now also have a REST API, which looks awesome, but I already had the code for using SOAP ).

I should also note that before I took on this work, I looked at Matt Doar's python-based CLI, which worked well for single commands (and was a reference for some of my jiranemo updates), but it didn't have a library interface, and it seemed very inefficient to keep spawning new python processes for thousands of commands.  Jiranemo's separation of the command-line option handling and config file parsing from the client library and helper functions make it fantastic for integrating into more complex python apps.

I expect that the next phase of development for jiranemo will be a gradual migration toward the REST APIs.  If this code is useful to you and you'd like to contribute to this effort, feel free to fork my bitbucket repo and send me pull requests.