jirarest2 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,55 @@
1
+ # Module to handle configuration files
2
+ # Copyright (C) 2012 Cyril Bitterich
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+
18
+ =begin
19
+ Module to handle configuration files
20
+ =end
21
+ module Config
22
+ =begin
23
+ Inspired by http://www.erickcantwell.com/2011/01/simple-configuration-file-reading-with-ruby/
24
+ reads a config-file and returns a hash
25
+ =end
26
+ def self.read_configfile(config_file)
27
+ config_file = File.expand_path(config_file)
28
+
29
+ unless File.exists?(config_file) then
30
+ raise "Unable to find config file"
31
+ end
32
+
33
+ regexp = Regexp.new(/\s+|"|\[|\]/)
34
+
35
+ temp = Array.new
36
+ vars = Hash.new
37
+
38
+ IO.foreach(config_file) { |line|
39
+ if line.match(/^\s*#/) # don't care about lines starting with an # (even after whitespace)
40
+ next
41
+ elsif line.match(/^\s*$/) # no text, no content
42
+ next
43
+ else
44
+ # Right now I don't know what to use scan for. It will escape " nice enough. But once that is excaped the regexp doesn't work any longer.
45
+ # temp[0],temp[1] = line.to_s.scan(/^.*$/).to_s.split("=")
46
+ temp[0],temp[1] = line.to_s.split("=")
47
+ temp.collect! { |val|
48
+ val.gsub(regexp, "")
49
+ }
50
+ vars[temp[0]] = temp[1]
51
+ end
52
+ }
53
+ return vars
54
+ end
55
+ end
@@ -0,0 +1,99 @@
1
+ # class to handle connections
2
+ # Copyright (C) 2012 Cyril Bitterich
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+
18
+
19
+ require 'net/http'
20
+ require 'exceptions'
21
+ require 'jirarest2/result'
22
+ require "pp"
23
+
24
+
25
+ =begin
26
+ An Connect object encasulates the connection to jira via REST. It takes an Credentials object and returns a Jirarest2::Result object or an exception if something went wrong.
27
+ =end
28
+ class Connect
29
+
30
+ =begin
31
+ Create an instance of Connect. It needs an Credentials object to be created.
32
+ =end
33
+ def initialize(credentials)
34
+ @pass = credentials.password
35
+ @user = credentials.username
36
+ @CONNECTURL = credentials.connecturl
37
+ end
38
+
39
+
40
+ =begin
41
+ Execute the request
42
+ * operation = one of Get, Post, Delete, Put
43
+ * uritail = the last part of the REST URI
44
+ * data = data to be sent.
45
+ =end
46
+ def execute(operation,uritail,data)
47
+ uri = nil
48
+ uri = URI(@CONNECTURL+uritail)
49
+
50
+ if data != "" then
51
+ if operation != "Post" then # POST carries the payload in the body that's why we have to wait
52
+ uri.query = URI.encode_www_form(data)
53
+ end
54
+ end
55
+
56
+ req = nil
57
+ req = Net::HTTP::const_get(operation).new(uri.request_uri) # "Classes Are Just Obejects, Too" (Design Patterns in Ruby, Russ Olsen, Addison Wessley)
58
+ req.basic_auth @user, @pass
59
+ req["Content-Type"] = "application/json;charset=UTF-8"
60
+
61
+ if data != "" then
62
+ if operation == "Post" then # POST carries the payload in the body
63
+ @payload = data.to_json
64
+ req.body = @payload
65
+ end
66
+ end
67
+
68
+ # Ask the server
69
+ result = Net::HTTP.start(uri.host, uri.port) {|http|
70
+ http.request(req)
71
+ }
72
+ # deal with output
73
+ case result
74
+ when Net::HTTPUnauthorized #No login-credentials oder wrong ones.
75
+ raise Jirarest2::AuthentificationError, result.body
76
+ when Net::HTTPForbidden #Captcha-Time
77
+ # pp res.get_fields("x-authentication-denied-reason")
78
+ # Result: ["CAPTCHA_CHALLENGE; login-url=http://localhost:8080/login.jsp"]
79
+ result.get_fields("x-authentication-denied-reason")[0] =~ /.*login-url=(.*)/
80
+ raise Jirarest2::AuthentificationCaptchaError, $1
81
+ end
82
+
83
+ return Jirarest2::Result.new(result)
84
+ end # execute
85
+
86
+
87
+ end # class
88
+
89
+ =begin
90
+
91
+
92
+ # Add a Key-Value for every search parameter you'd usually have.
93
+ query={"jql"=>"project = MFTP", "startAt"=>0, "maxResults"=>4 }
94
+ #pp query
95
+ # Here we query all those that match our parameter.
96
+ #result = get_response("search",query)
97
+ #pp result
98
+
99
+ =end
@@ -0,0 +1,62 @@
1
+ # class to get the credentials together
2
+ # Copyright (C) 2012 Cyril Bitterich
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+
18
+
19
+ require "uri"
20
+
21
+ =begin
22
+ A Credentials object contains the data required to connect to a JIRA(tm) instance.
23
+ =end
24
+ class Credentials
25
+
26
+ # username to use
27
+ attr_accessor :username
28
+ # password for the connection
29
+ attr_accessor :password
30
+ # url to connect to the JIRA(tm) instance
31
+ attr_reader :connecturl
32
+
33
+ =begin
34
+ Create an instance of Credentials.
35
+ Requires username, password and the url for the JIRA(tm) instance. (The URL should stop after the port. There is no guarantee it would work if there is any path component given.)
36
+ =end
37
+ def initialize(url,username,password)
38
+ @username = username
39
+ @password = password
40
+ uri = URI(url)
41
+ if uri.instance_of?(URI::HTTP) || uri.instance_of?(URI::HTTPS) then
42
+ @connecturl = url
43
+ else
44
+ raise Jirarest2::NotAnURLError
45
+ end
46
+ end
47
+
48
+ =begin
49
+ Setter for the URL.
50
+
51
+ Throws an Jirarest2::NotAnURLError if the given String is not an URI.
52
+ =end
53
+ def connecturl=(url)
54
+ uri = URI(url)
55
+ if uri.instance_of?(URI::HTTP) || uri.instance_of?(URI::HTTPS) then
56
+ @connecturl = url
57
+ else
58
+ raise Jirarest2::NotAnURLError
59
+ end
60
+ end
61
+
62
+ end
@@ -0,0 +1,47 @@
1
+ # module to handle all the exceptions in their own namespace
2
+ # Copyright (C) 2012 Cyril Bitterich
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+
18
+ =begin
19
+ Keep all the Exceptions in their own module
20
+ =end
21
+ module Jirarest2
22
+ ## connections.rb
23
+
24
+ # Authentification failed
25
+ class AuthentificationError < StandardError ; end
26
+ # Authentification failed and JIRA(tm) requires a login with captcha to continue
27
+ class AuthentificationCaptchaError < StandardError ; end
28
+
29
+ ## credentials.rb
30
+
31
+ # String given as an URI isn't one
32
+ class NotAnURLError < ArgumentError ; end
33
+
34
+ ## issue.rb
35
+
36
+ # Project does not exist in the given JIRA(tm) instance
37
+ class WrongProjectException < ArgumentError; end
38
+ # Issue type does not exist in the given project
39
+ class WrongIssuetypeException < ArgumentError; end
40
+ # There is no field with this name for the given issue type
41
+ class WrongFieldnameException < ArgumentError; end
42
+ # value is not allowed for this type of fields
43
+ class ValueNotAllowedException < ArgumentError; end
44
+ # A field that is defined as "required" has not been given a value
45
+ class RequiredFieldNotSetException < ArgumentError; end
46
+
47
+ end
@@ -0,0 +1,266 @@
1
+ # class to handle new issues (building them up, changing fields, persistence)
2
+ # Copyright (C) 2012 Cyril Bitterich
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+
18
+ # TODO always recreating Connection is not really DRY - Find a way to fix it.
19
+
20
+ =begin
21
+ An Issue object contains all the data of an issue
22
+ =end
23
+ class Issue
24
+
25
+ require "connect"
26
+
27
+ # issue type of the issue
28
+ attr_reader :issuetype
29
+ # project the issue belongs to
30
+ attr_reader :project
31
+ # The issue numer if we got it somehow
32
+ attr_reader :issuekey
33
+
34
+ =begin
35
+ New initialize method we take the project and the type we want to use and take login info that might exist just right with us
36
+ project Name of the project this issue is to live in
37
+ =end
38
+ def initialize (project,type,credentials)
39
+ connection = Connect.new(credentials)
40
+ query = {:projectKeys => project, :issuetypeNames => type, :expand => "projects.issuetypes.fields" }
41
+ answer = connection.execute("Get","issue/createmeta/",query)
42
+ jhash = answer.result
43
+ parse_json(jhash)
44
+ raise Jirarest2::WrongProjectException, project if @project == ""
45
+ raise Jirarest2::WrongIssuetypeException, type if @issuetype == ""
46
+ end
47
+
48
+ =begin
49
+ It needs the hashed version of the json-snippet jira returns
50
+ It produces an instance-variable @issuefields that can be
51
+ =end
52
+ def parse_json (jhash)
53
+ @issuefields = Hash.new
54
+ @project = ""
55
+ @issuetype = ""
56
+ jhash["projects"].each { |value|
57
+ @project = value["key"]
58
+ value["issuetypes"].each { |value|
59
+ @issuetype = value["name"]
60
+ value["fields"].delete("project") #The project key is duplicate and will make us live harder afterwards. It is marked as required but nothing happens if this key is not set.
61
+ value["fields"].each { |key,value|
62
+ fields = Hash.new
63
+ fields["id"] = key
64
+ if value["name"] then
65
+ name = value["name"]
66
+ else
67
+ name = key
68
+ end
69
+ # If the allowed reponses are limited we want to know them.
70
+ if value["allowedValues"] then
71
+ # With custom fields the identifier is "value" with the built in ones it's "name"
72
+ identifier = "name"
73
+ if value["schema"]["custom"] then
74
+ identifier = "value"
75
+ end
76
+ allowedValues = Array.new
77
+ value["allowedValues"].each { |value|
78
+ allowedValues << value[identifier]
79
+ }
80
+ fields["allowedValuesIdentifier"] = identifier
81
+ fields["allowedValues"] = allowedValues
82
+ end
83
+ fields["required"] = value["required"]
84
+ fields["type"] = value["schema"]["type"]
85
+ @issuefields[name] = fields if name != "Issue Type" # "Issue Type" is not really a required field as we have to assign it at another place anyway
86
+ }
87
+ }
88
+ }
89
+ end
90
+
91
+ =begin
92
+ Return the type of a field
93
+ =end
94
+ def fieldtype(field)
95
+ # If the fieldname is wrong we want to tell this and stop execution (or maybe let the caller fix it)
96
+ if @issuefields[field].nil? then
97
+ raise Jirarest2::WrongFieldnameException, field
98
+ else
99
+ return @issuefields[field]["type"]
100
+ end
101
+ end
102
+
103
+ =begin
104
+ Return all the fields that are required for this issuetype
105
+ =end
106
+ def get_requireds
107
+ names = Array.new
108
+ @issuefields.each {|key,value|
109
+ if value["required"] then
110
+ names << key
111
+ end
112
+ }
113
+ return names
114
+ end
115
+
116
+ =begin
117
+ Return all the names of the fields
118
+ =end
119
+ def get_fieldnames
120
+ names = Array.new
121
+ @issuefields.each {|key,value|
122
+ names << key
123
+ }
124
+ return names
125
+ end
126
+
127
+ =begin
128
+ return the id of a field name
129
+ =end
130
+ protected
131
+ def get_id(name)
132
+ return @issuefields["name"]["id"]
133
+ end
134
+
135
+ =begin
136
+ return a hash that can be sent to jira
137
+ Lets create a new issue
138
+ query=
139
+ {"fields"=>
140
+ { "project"=>{"key"=>"MFTP"},
141
+ "environment"=>"REST ye merry gentlemen.",
142
+ "My own text"=>"Creating of an issue using project keys and issue type names using the REST API",
143
+ "issuetype"=> {"name"=>"My own type"}
144
+ }
145
+ }
146
+ =end
147
+ public
148
+ def jirahash
149
+ h = Hash.new
150
+ issuetype = {"issuetype" => {"name" => @issuetype}}
151
+ project = {"key" => @project}
152
+ fields = Hash.new
153
+ fields["project"] = project
154
+ # here we need to enter the relevant fields and their values
155
+ @issuefields.each { |key,value|
156
+ if key != "project" then
157
+ id = value["id"]
158
+ if ! value["value"].nil? then
159
+ fields[id] = value["value"]
160
+ end
161
+ end
162
+ }
163
+ fields = fields.merge!(issuetype)
164
+ h = {"fields" => fields}
165
+ return h
166
+ end
167
+
168
+ =begin
169
+ checks if the value is allowed for this field
170
+ =end
171
+ protected
172
+ def value_allowed?(key,value)
173
+ if @issuefields[key]["allowedValues"].include?(value)
174
+ return true
175
+ else
176
+ raise Jirarest2::ValueNotAllowedException, @issuefields[key]["allowedValues"]
177
+ puts "Value #{value} not allowed for field #{key}."
178
+ end
179
+ end
180
+
181
+ =begin
182
+ Special setter for fields that have a limited numer of allowed values.
183
+
184
+ This setter might be included in set_field at a later date.
185
+ =end
186
+ def set_allowed_value(key,value)
187
+ if @issuefields[key]["type"] == "array" && value.instance_of?(Array) then
188
+ array = Array.new
189
+ value.each {|item|
190
+ if value_allowed?(key,item) then
191
+ array << {@issuefields[key]["allowedValuesIdentifier"] => item}
192
+ end
193
+ }
194
+ @issuefields[key]["value"] = array
195
+ else
196
+ if value_allowed?(key,value) then
197
+ @issuefields[key]["value"] = {@issuefields[key]["allowedValuesIdentifier"] => value}
198
+ end
199
+ end
200
+ end
201
+
202
+ =begin
203
+ Setter fuer die Felder des Issues
204
+ :key is the name of the field
205
+ :value is is the value the field should get , this is either a String or an Array (don't know if numbers work too)
206
+ TODO We are not yet able to work with "Cascading Select" fields ( "custom": "com.atlassian.jira.plugin.system.customfieldtypes:cascadingselect")
207
+ =end
208
+ public
209
+ def set_field(key, value)
210
+ if @issuefields.include?(key) then
211
+ if @issuefields[key].include?("allowedValues") then
212
+ set_allowed_value(key,value)
213
+ else
214
+ @issuefields[key]["value"] = value
215
+ end
216
+ else
217
+ raise Jirarest2::WrongFieldnameException, key
218
+ puts "Unknown Field: #{key}"
219
+ end
220
+ end
221
+
222
+ =begin
223
+ Get the value of a certain field
224
+ =end
225
+ def get_field(field)
226
+ @issuefields[field]["value"]
227
+ end
228
+
229
+ =begin
230
+ create a new ticket
231
+ =end
232
+ def persist(credentials)
233
+ get_requireds.each { |fieldname|
234
+ if @issuefields[fieldname]["value"].nil? then
235
+ raise Jirarest2::RequiredFieldNotSetException, fieldname
236
+ end
237
+ }
238
+ connection = Connect.new(credentials)
239
+ hash = jirahash
240
+ # TODO we changed the returning Value here - it's an Result object and no longer just the equivalent of Result.result
241
+ ret = connection.execute("Post","issue/",hash)
242
+ if ret.code == "201" then # Ticket sucessfully created
243
+ @issuekey = ret.result["key"]
244
+ end
245
+ return ret
246
+ end
247
+
248
+
249
+ =begin
250
+ Set the watchers for this Ticket
251
+ watchers has to be an Array
252
+ =end
253
+ def add_watchers(credentials,watchers)
254
+ success = false # Return whether we were successful with the watchers
255
+ connection = Connect.new(credentials)
256
+ watch = Watcher.new(connection,@issuekey)
257
+ watchers.each { |person|
258
+ success = watch.add_watcher(person)
259
+ }
260
+ return success
261
+ end
262
+
263
+
264
+ end # class
265
+
266
+