Github, JIRA: Play Nice Together!

At Lexity (where I work and you should too!) we recently had a hack day. I’ve just put the results of my efforts up on Github in a repository called git_post_receive, available at http://github.com/pariser/git_post_receive.

I have always disliked the fact that Github and JIRA speak different languages. If you don’t know what I’m talking about, then you’re probably thankful that you are not the target demographic of this post, but in case you want a bit more understanding about what I’m talking about, we use Github to manage our code and JIRA to manage our bugs and issues). That terrible “Men are from Mars…” idiom applies all too to these two systems…

You would think that when a JIRA issue is referenced in a Github commit message, that the bug would be updated with information about the associated commit. This project, git_post_receive, is a lightweight server I wrote which will do just that, hence increasing my (and hopefully your) productivity!

The Hack

There are three components to my hack

  • Using JIRA’s APIs to comment on and change the status of issues.
  • Using Github’s great post-receive hook to post to a server.
  • Reading commit information and bridging the two APIs.

The first step was deciding which language I’d write everything in, from Python, Ruby or Node. A bit of research revealed that the jira4r Ruby Gem would probably be the cleanest way for me to get access to the JIRA APIs. I would soon find out that even though jira4r was likely the “cleanest” option, there is sparse documentation to make it easy to use.

Because I was using going to use Ruby, I figured I would write a small Rails application that would listen for incoming POSTs that Github will fire upon receiving new commits to a repository (see here for documentation, but note that in my experience, the actual POST data will have a superset of the fields described in the docs).

The rest, I might say, is a matter of plumbing it all together.

All of the heavy lifting is inside the PostreceiveController‘s create method (and the protected methods jira and jira_projects):

  require 'jira4r/jira_tool.rb'
  require 'time'
  require 'yaml'

  def create
    @payload = ActiveSupport::JSON.decode params[:payload]
    @commented_issues = Set.new
    @resolved_issues = Set.new

    # Simple lookup for whether to resolve an issue
    project_keys = jira_projects.map {|p| p.key}

    r = Regexp.new('(fixe?[sd]?|resolve[sd]?)? (%s)-([0-9]+)' % [project_keys.join('|')], Regexp::IGNORECASE)

    @payload["commits"].each do |commit|

      begin
        time = Time.parse commit["timestamp"]
      rescue
        time = Time.now
      end

      rc = Jira4R::V2::RemoteComment.new
      rc.body = "Found related commit [%s] by %s (%s) at %s\n\n%s\n\n<Message auto-added by pariser's git post-receive hook magic>" \
      % [ commit["url"], commit["author"]["name"], commit["author"]["email"], time.to_s, commit["message"] ]

      commit["message"].scan(r) do |match|

        should_resolve_issue = !match[0].nil?
        issue_key = match[1] + "-" + match[2]

        # Comment on this issue
        begin
          jira.addComment(issue_key, rc)
        rescue
          Rails.logger.error("Failed to add comment to issue %s" % [issue_key])
        else
          Rails.logger.debug("Successfully added comment to issue %s" % [issue_key])
        end

        # Resolve this issue, as appropriate
        if should_resolve_issue
          begin
            available_actions = jira.getAvailableActions issue_key
            resolve_action = available_actions.find {|s| s.name == 'Resolve Issue'}
            if !resolve_action.nil?
              jira.progressWorkflowAction(issue_key, resolve_action.id.to_s, [])
            else
              Rails.logger.debug("Not allowed to resolve issue %s. Allowable actions: %s" % [issue_key, (available_actions.map {|s| s.name}).to_s])
            end
          rescue StandardError => e
            Rails.logger.error("Failed to resolve issue %s : %s" % [issue_key, e.to_s])
          else
            Rails.logger.debug("Successfully resolved issue %s" % [issue_key])
          end
        end

      end
    end
  end

  protected

  def jira
    # Connect to JIRA

    # Load configuration
    unless @jira_config
      @jira_config = YAML.load(File.new "config/jira.yml", 'r')
    end

    # Connect to JIRA
    unless @jira_connection
      @jira_connection = Jira4R::JiraTool.new(2, @jira_config['address'])
      @jira_connection.login(@jira_config['username'], @jira_config['password'])
    end

    # Return the connection
    @jira_connection
  end

  def jira_projects
    # Load the list of JIRA projects
    unless @jira_projects
      @jira_projects = jira.getProjectsNoSchemes()
    end

    @jira_projects
  end

Install This Project

You may only come here because you want to install this project. Follow these (not yet tested) steps, and you should be able to install it git_post_receive with (hopefully) minimal headache:

  • Have Rails & Bundler installed.
  • Clone the repository via git clone https://github.com/pariser/git_post_receive.git
  • From the root project folder,
  • run bundle install
  • run cp config/jira-TEMPLATE.yml config/jira.yml
  • Edit config/jira.yml using your favorite editor, and put your JIRA server location and authentication credentials in this file.
  • Run rails s to serve the application, defaults to port 3000 (default)
  • Go to your Github repository when logged in as a repository administrator.
  • Click on Admin > Service Hooks > Post-Receive URLs
  • Add the url http://server:3000/postreceive
  • Commit to the repository with a message that references a JIRA issue on your server, and watch the magic in action!

(Please do comment, email me or tweet at me if these instructions leave you wanting. I’ll do my best to help you out!)

The Disclaimer

I should explain that I’m not doing something groundbreaking. You can currently achieve the same outcome of my Github-JIRA connectivity layer by paying a decent chunk of change to pay for FishEye. (FishEye is made by Atlassian, the same company that makes JIRA whose name I cannot help but read as “alt assassin”. As if JIRA is not expensive enough, they want you to pay more to allow you to use seemingly obvious features.) The thing I am doing, however, is encouraging you to play with these great APIs!

Go forth and use (or fork) my project!

This entry was posted in Projects and tagged , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *