Add JiraAgent

The agent does periodical queries and emits
the events containing the updated issues in JSON format.

The agent also supports HTTP basic authentication.

Konstantin Nazarov 11 年 前
コミット
4fec08a6e0
共有1 個のファイルを変更した125 個の追加0 個の削除を含む
  1. 125 0
      app/models/agents/jira_agent.rb

+ 125 - 0
app/models/agents/jira_agent.rb

@@ -0,0 +1,125 @@
1
+#!/usr/bin/env ruby
2
+
3
+require 'cgi'
4
+require 'httparty'
5
+require 'date'
6
+
7
+module Agents
8
+  class JiraAgent < Agent
9
+    cannot_receive_events!
10
+
11
+    description <<-MD
12
+      The Jira Agent subscribes to Jira issue updates.
13
+
14
+      `jira_url` specidies the full URL of the jira installation, including https://
15
+      `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. 
16
+      `username` and `password` are optional, and may need to be specified if your Jira instance is read-protected
17
+
18
+      The agent does periodical queries and emits the events containing the updated issues in JSON format.
19
+      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.
20
+    MD
21
+
22
+    event_description <<-MD
23
+      Events are the raw JSON generated by Jira REST API
24
+
25
+      {
26
+        "expand": "editmeta,renderedFields,transitions,changelog,operations",
27
+        "id": "80127",
28
+        "self": "https://jira.atlassian.com/rest/api/2/issue/80127",
29
+        "key": "BAM-3512",
30
+        "fields": {
31
+          ...
32
+        }
33
+      }
34
+    MD
35
+
36
+    default_schedule "every_10m"
37
+
38
+    def default_options
39
+      {
40
+        'username'  => '',
41
+        'password' => '',
42
+        'jira_url' => 'https://jira.atlassian.com',
43
+        'jql' => '' 
44
+      }
45
+    end
46
+
47
+    def validate_options
48
+        errors.add(:base, "you need to specify your jira URL") unless options['jira_url'].present?
49
+    end
50
+
51
+    def working?
52
+      (events_count.present? && events_count > 0)
53
+    end
54
+
55
+    def check
56
+      last_run = nil
57
+
58
+      current_run = Time.now.utc.iso8601
59
+      last_run = Time.parse(memory[:last_run]) if memory[:last_run]
60
+      begin
61
+        issues = get_issues(last_run)
62
+
63
+        issues.each do |issue|
64
+          create_event :payload => issue
65
+        end
66
+
67
+        memory[:last_run] = current_run
68
+      rescue Exception => e
69
+        log(e.message)
70
+      end
71
+    end
72
+
73
+  private
74
+    def request_url(jql, start_at)
75
+      "#{options[:jira_url]}/rest/api/2/search?jql=#{CGI::escape(jql)}&fields=*all&startAt=#{start_at}"
76
+    end
77
+
78
+    def request_options
79
+      ropts = {:headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)"}}
80
+
81
+      if !options[:username].empty?  then
82
+        ropts = ropts.merge({:basic_auth => {:username =>options[:username], :password=>options[:password]}})
83
+      end
84
+
85
+      ropts
86
+    end
87
+
88
+
89
+    def get_issues(since)
90
+      startAt = 0
91
+      issues = []
92
+
93
+      jql = ""
94
+
95
+      if !options[:jql].empty? && since then 
96
+        jql = "(#{options[:jql]}) and updated >= '#{since.strftime('%Y-%m-%d %H:%M')}'"
97
+      else
98
+        jql = options[:jql] if !options[:jql].empty?
99
+        jql = "updatedd >= '#{since.strftime('%Y-%m-%d %H:%M')}'" if since
100
+      end
101
+
102
+
103
+      loop do
104
+        response = HTTParty.get(request_url(jql, startAt), request_options)
105
+
106
+        if response.code == 400 then
107
+          raise RuntimeError.new("Jira error: #{response['errorMessages']}") 
108
+        elsif response.code == 403 then
109
+          raise RuntimeError.new("Authentication failed: Forbidden (403)")
110
+        elsif response.code != 200 then
111
+          raise RuntimeError.new("Request failed: #{response}")
112
+        end
113
+
114
+        issues += response['issues']
115
+        startAt += response['issues'].length
116
+ 
117
+        break if startAt >= response['total']
118
+      end
119
+
120
+      issues
121
+    end
122
+
123
+  end
124
+end
125
+