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!