jira_agent.rb 3.9KB

    #!/usr/bin/env ruby require 'cgi' require 'httparty' require 'date' module Agents class JiraAgent < Agent cannot_receive_events! description <<-MD The Jira Agent subscribes to Jira issue updates. `jira_url` specidies the full URL of the jira installation, including https:// `jql` is an optional Jira Query Language-based filter to limit the flow of events. See [JQL Docs](https://confluence.atlassian.com/display/JIRA/Advanced+Searching) for details. `username` and `password` are optional, and may need to be specified if your Jira instance is read-protected The agent does periodical queries and emits the events containing the updated issues in JSON format. NOTE: upon the first execution, the agent will fetch everything available by the JQL query. So if it's not desirable, limit the `jql` query by date. MD event_description <<-MD Events are the raw JSON generated by Jira REST API { "expand": "editmeta,renderedFields,transitions,changelog,operations", "id": "80127", "self": "https://jira.atlassian.com/rest/api/2/issue/80127", "key": "BAM-3512", "fields": { ... } } MD default_schedule "every_10m" def default_options { 'username' => '', 'password' => '', 'jira_url' => 'https://jira.atlassian.com', 'jql' => '' } end def validate_options errors.add(:base, "you need to specify your jira URL") unless options['jira_url'].present? end def working? (events_count.present? && events_count > 0) end def check last_run = nil current_run = Time.now.utc.iso8601 last_run = Time.parse(memory[:last_run]) if memory[:last_run] begin issues = get_issues(last_run) issues.each do |issue| updated = Time.parse(issue['fields']['updated']) # this check is more precise than in get_issues() # see get_issues() for explanation if updated > last_run then create_event :payload => issue end end memory[:last_run] = current_run rescue Exception => e log(e.message) end end private def request_url(jql, start_at) "#{options[:jira_url]}/rest/api/2/search?jql=#{CGI::escape(jql)}&fields=*all&startAt=#{start_at}" end def request_options ropts = {:headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)"}} if !options[:username].empty? then ropts = ropts.merge({:basic_auth => {:username =>options[:username], :password=>options[:password]}}) end ropts end def get_issues(since) startAt = 0 issues = [] # JQL doesn't have an ability to specify timezones # Because of this we have to fetch issues 24 h # earlier and filter out unnecessary ones at a later # stage. Fortunately, the 'updated' field has GMT # offset since -= 24*60*60 jql = "" if !options[:jql].empty? && since then jql = "(#{options[:jql]}) and updated >= '#{since.strftime('%Y-%m-%d %H:%M')}'" else jql = options[:jql] if !options[:jql].empty? jql = "updated >= '#{since.strftime('%Y-%m-%d %H:%M')} GMT'" if since end loop do response = HTTParty.get(request_url(jql, startAt), request_options) if response.code == 400 then raise RuntimeError.new("Jira error: #{response['errorMessages']}") elsif response.code == 403 then raise RuntimeError.new("Authentication failed: Forbidden (403)") elsif response.code != 200 then raise RuntimeError.new("Request failed: #{response}") end issues += response['issues'] startAt += response['issues'].length break if startAt >= response['total'] end issues end end end