rt-client 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. metadata +6 -7
  2. data/rt/client.rb +0 -779
  3. data/rt/rtxmlsrv.rb +0 -261
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rt-client
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 2
10
- version: 0.3.2
9
+ - 3
10
+ version: 0.3.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tom Lahti
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-14 00:00:00 -07:00
18
+ date: 2010-10-25 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -133,9 +133,8 @@ extensions: []
133
133
 
134
134
  extra_rdoc_files: []
135
135
 
136
- files:
137
- - rt/client.rb
138
- - rt/rtxmlsrv.rb
136
+ files: []
137
+
139
138
  has_rdoc: true
140
139
  homepage:
141
140
  licenses: []
data/rt/client.rb DELETED
@@ -1,779 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- require "rubygems"
4
- require "rest_client"
5
- require "tmail"
6
- require "iconv"
7
- require 'mime/types' # requires both nokogiri and rcov. Yuck.
8
- require 'date'
9
- require 'pp'
10
-
11
- ##A ruby library API to Request Tracker's REST interface. Requires the
12
- ##rubygems rest-client, tmail and mime-types to be installed. You can
13
- ##create a file name .rtclientrc in the same directory as client.rb with a
14
- ##default server/user/pass to connect to RT as, so that you don't have to
15
- ##specify it/update it in lots of different scripts.
16
- ##
17
- ##TODO: Streaming, chunking attachments in compose method
18
- #
19
- # See each method for sample usage. To use this, "gem install rt-client" and
20
- #
21
- # require "rt/client"
22
-
23
- class RT_Client
24
-
25
- UA = "Mozilla/5.0 ruby RT Client Interface 0.3.2"
26
- attr_reader :status, :site, :version, :cookies, :server, :user, :cookie
27
-
28
- # Create a new RT_Client object. Load up our stored cookie and check it.
29
- # Log into RT again if needed and store the new cookie. You can specify
30
- # login and cookie storage directories in 3 different ways:
31
- # 1. Explicity during object creation
32
- # 2. From a .rtclientrc file in the working directory of your ruby program
33
- # 3. From a .rtclientrc file in the same directory as the library itself
34
- #
35
- # These are listed in order of priority; if you have explicit parameters,
36
- # they are always used, even if you have .rtclientrc files. If there
37
- # is both an .rtclientrc in your program's working directory and
38
- # in the library directory, the one from your program's working directory
39
- # is used. If no parameters are specified either explicity or by use
40
- # of a .rtclientrc, then the defaults of "rt_user", "rt_pass" are used
41
- # with a default server of "http://localhost", and cookies are stored
42
- # in the directory where the library resides.
43
- #
44
- # rt= RT_Client.new( :server => "https://tickets.ambulance.com/",
45
- # :user => "rt_user",
46
- # :pass => "rt_pass",
47
- # :cookies => "/my/cookie/dir" )
48
- #
49
- # rt= RT_Client.new # use defaults from .rtclientrc
50
- #
51
- # .rtclientrc format:
52
- # server=<RT server>
53
- # user=<RT user>
54
- # pass=<RT password>
55
- # cookies=<directory>
56
- def initialize(*params)
57
- @boundary = "----xYzZY#{rand(1000000).to_s}xYzZY"
58
- @version = "0.3.2"
59
- @status = "Not connected"
60
- @server = "http://localhost/"
61
- @user = "rt_user"
62
- @pass = "rt_pass"
63
- @cookies = Dir.pwd
64
- config_file = Dir.pwd + "/.rtclientrc"
65
- config = ""
66
- if File.file?(config_file)
67
- config = File.read(config_file)
68
- else
69
- config_file = File.dirname(__FILE__) + "/.rtclientrc"
70
- config = File.read(config_file) if File.file?(config_file)
71
- end
72
- @server = $~[1] if config =~ /\s*server\s*=\s*(.*)$/i
73
- @user = $~[1] if config =~ /^\s*user\s*=\s*(.*)$/i
74
- @pass = $~[1] if config =~ /^\s*pass\s*=\s*(.*)$/i
75
- @cookies = $~[1] if config =~ /\s*cookies\s*=\s*(.*)$/i
76
- @resource = "#{@server}REST/1.0/"
77
- if params.class == Array && params[0].class == Hash
78
- param = params[0]
79
- @user = param[:user] if param.has_key? :user
80
- @pass = param[:pass] if param.has_key? :pass
81
- if param.has_key? :server
82
- @server = param[:server]
83
- @server += "/" if @server !~ /\/$/
84
- @resource = "#{@server}REST/1.0/"
85
- end
86
- @cookies = param[:cookies] if param.has_key? :cookies
87
- end
88
- @login = { :user => @user, :pass => @pass }
89
- cookiejar = "#{@cookies}/RT_Client.#{@user}.cookie" # cookie location
90
- cookiejar.untaint
91
- if File.file? cookiejar
92
- @cookie = File.read(cookiejar).chomp
93
- headers = { 'User-Agent' => UA,
94
- 'Content-Type' => "application/x-www-form-urlencoded",
95
- 'Cookie' => @cookie }
96
- else
97
- headers = { 'User-Agent' => UA,
98
- 'Content-Type' => "application/x-www-form-urlencoded" }
99
- @cookie = ""
100
- end
101
-
102
-
103
- site = RestClient::Resource.new(@resource, :headers => headers)
104
- data = site.post "" # a null post just to check that we are logged in
105
-
106
- if @cookie.length == 0 or data =~ /401/ # we're not logged in
107
- data = site.post @login, :headers => headers
108
- # puts data
109
- @cookie = data.headers[:set_cookie].to_s.split('; ')[0]
110
- # write the new cookie
111
- if @cookie !~ /nil/
112
- f = File.new(cookiejar,"w")
113
- f.puts @cookie
114
- f.close
115
- end
116
- end
117
- headers = { 'User-Agent' => UA,
118
- 'Content-Type' => "multipart/form-data; boundary=#{@boundary}",
119
- 'Cookie' => @cookie }
120
- @site = RestClient::Resource.new(@resource, :headers => headers)
121
- @status = data
122
- self.untaint
123
- end
124
-
125
- # gets the detail for a single ticket/user. If its a ticket, its without
126
- # history or attachments (to get those use the history method) . If no
127
- # type is specified, ticket is assumed. takes a single parameter
128
- # containing the ticket/user id, and returns a hash of RT Fields => values
129
- #
130
- # hash = rt.show(822)
131
- # hash = rt.show("822")
132
- # hash = rt.show("ticket/822")
133
- # hash = rt.show(:id => 822)
134
- # hash = rt.show(:id => "822")
135
- # hash = rt.show(:id => "ticket/822")
136
- # hash = rt.show("user/#{login}")
137
- # email = rt.show("user/somebody")["emailaddress"]
138
- def show(id)
139
- id = id[:id] if id.class == Hash
140
- id = id.to_s
141
- type = "ticket"
142
- sid = id
143
- if id =~ /(\w+)\/(.+)/
144
- type = $~[1]
145
- sid = $~[2]
146
- end
147
- reply = {}
148
- if type.downcase == 'user'
149
- resp = @site["#{type}/#{sid}"].get
150
- else
151
- resp = @site["#{type}/#{sid}/show"].get
152
- end
153
- resp.gsub!(/RT\/\d\.\d\.\d\s\d{3}\s.*\n\n/,"") # toss the HTTP response
154
- resp.gsub!(/\n\n/,"\n") # remove double spacing, TMail stops at a blank line
155
- return {:error => resp, } if resp =~ /does not exist./
156
- th = TMail::Mail.parse(resp)
157
- th.each_header do |k,v|
158
- reply["#{k}"] = v.to_s
159
- end
160
- reply
161
- end
162
-
163
- # gets a list of ticket links for a ticket.
164
- # takes a single parameter containing the ticket id,
165
- # and returns a hash of RT Fields => values
166
- #
167
- # hash = rt.links(822)
168
- # hash = rt.links("822")
169
- # hash = rt.links("ticket/822")
170
- # hash = rt.links(:id => 822)
171
- # hash = rt.links(:id => "822")
172
- # hash = rt.links(:id => "ticket/822")
173
- def links(id)
174
- id = id[:id] if id.class == Hash
175
- id = id.to_s
176
- type = "ticket"
177
- sid = id
178
- if id =~ /(\w+)\/(.+)/
179
- type = $~[1]
180
- sid = $~[2]
181
- end
182
- reply = {}
183
- resp = @site["ticket/#{sid}/links/show"].get
184
- resp.gsub!(/RT\/\d\.\d\.\d\s\d{3}\s.*\n\n/,"") # toss the HTTP response
185
- resp.gsub!(/\n\n/,"\n") # remove double spacing, TMail stops at a blank line
186
- return {:error => resp, } if resp =~ /does not exist./
187
- th = TMail::Mail.parse(resp)
188
- th.each_header do |k,v|
189
- reply["#{k}"] = v.to_s
190
- end
191
- reply
192
- end
193
-
194
- # Creates a new ticket. Requires a hash that contains RT form fields as
195
- # the keys. Capitalization is important; use :Queue, not :queue. You
196
- # will need at least :Queue to create a ticket. For a full list of fields
197
- # you can use, try "/opt/rt3/bin/rt edit ticket/1". Returns the newly
198
- # created ticket number, or a complete REST response.
199
- #
200
- # id = rt.create( :Queue => "Customer Service",
201
- # :Cc => "somebody\@email.com",
202
- # :Subject => "I've fallen and I can't get up",
203
- # :Text => "I think my hip is broken.\nPlease help.",
204
- # :"CF.{CustomField}" => "Urgent",
205
- # :Attachment => "/tmp/broken_hip.jpg" )
206
- def create(field_hash)
207
- field_hash[:id] = "ticket/new"
208
- payload = compose(field_hash)
209
- puts "Payload for new ticket:"
210
- puts payload
211
- resp = @site['ticket/new/edit'].post payload
212
- new_id = resp.match(/Ticket\s*(\d+)/)
213
- if new_id.class == MatchData
214
- new_ticket = new_id[1]
215
- else
216
- new_ticket = resp
217
- end
218
- new_ticket # return the ticket number, or the full REST response
219
- end
220
-
221
- # create a new user. Requires a hash of RT fields => values. Returns
222
- # the newly created user ID, or the full REST response if there is an error.
223
- # For a full list of possible parameters that you can specify, look at
224
- # "/opt/rt/bin/rt edit user/1"
225
- #
226
- # new_id = rt.create_user(:Name => "Joe_Smith", :EmailAddress => "joes\@here.com")
227
- def create_user(field_hash)
228
- field_hash[:id] = "user/new"
229
- payload = compose(field_hash)
230
- resp = @site['user/new/edit'].post payload
231
- new_id = resp.match(/User\s*(\d+)/)
232
- if new_id.class == MatchData
233
- new_user = new_id[1]
234
- else
235
- new_user = resp
236
- end
237
- new_user # return the new user id or the full REST response
238
- end
239
-
240
- # edit or create a user. If the user exists, edits the user as "edit" would.
241
- # If the user doesn't exist, creates it as "create_user" would.
242
- def edit_or_create_user(field_hash)
243
- if field_hash.has_key? :id
244
- id = field_hash[:id]
245
- if id !~ /^user\//
246
- id = "user/#{id}"
247
- field_hash[:id] = id
248
- end
249
- else
250
- raise "RT_Client.edit_or_create_user require a user id in the 'id' key."
251
- end
252
- resp1 = "not called"
253
- resp2 = "not called"
254
- resp1 = edit(field_hash)
255
- resp2 = create_user(field_hash) if resp1 =~ /does not exist./
256
- resp = "Edit: #{resp1}\nCreate:#{resp2}"
257
- resp
258
- end
259
-
260
- # edit an existing ticket/user. Requires a hash containing RT
261
- # form fields as keys. the key :id is required.
262
- # returns the complete REST response, whatever it is. If the
263
- # id supplied contains "user/", it edits a user, otherwise
264
- # it edits a ticket. For a full list of fields you can edit,
265
- # try "/opt/rt3/bin/rt edit ticket/1"
266
- #
267
- # resp = rt.edit(:id => 822, :Status => "resolved")
268
- # resp = rt.edit(:id => ticket_id, :"CF.{CustomField}" => var)
269
- # resp = rt.edit(:id => "user/someone", :EMailAddress => "something@here.com")
270
- # resp = rt.edit(:id => "user/bossman", :Password => "mypass")
271
- # resp = rt.edit(:id => "user/4306", :Disabled => "1")
272
- def edit(field_hash)
273
- if field_hash.has_key? :id
274
- id = field_hash[:id]
275
- else
276
- raise "RT_Client.edit requires a user or ticket id in the 'id' key."
277
- end
278
- type = "ticket"
279
- sid = id
280
- if id =~ /(\w+)\/(.+)/
281
- type = $~[1]
282
- sid = $~[2]
283
- end
284
- payload = compose(field_hash)
285
- resp = @site["#{type}/#{sid}/edit"].post payload
286
- resp
287
- end
288
-
289
- # Comment on a ticket. Requires a hash, which must have an :id key
290
- # containing the ticket number. Returns the REST response. For a list of
291
- # fields you can use in a comment, try "/opt/rt3/bin/rt comment ticket/1"
292
- #
293
- # rt.comment( :id => id,
294
- # :Text => "Oh dear, I wonder if the hip smells like almonds?",
295
- # :Attachment => "/tmp/almonds.gif" )
296
- def comment(field_hash)
297
- if field_hash.has_key? :id
298
- id = field_hash[:id]
299
- else
300
- raise "RT_Client.comment requires a Ticket number in the 'id' key."
301
- end
302
- field_hash[:Action] = "comment"
303
- payload = compose(field_hash)
304
- @site["ticket/#{id}/comment"].post payload
305
- end
306
-
307
- def usersearch(field_hash)
308
- if field_hash.has_key? :EmailAddress
309
- email = field_hash[:EmailAddress]
310
- else
311
- raise "RT_Client.usersearch requires a user email in the 'EmailAddress' key."
312
- end
313
- payload = compose(field_hash)
314
- resp = @site["search/user/?query=EmailAddress='#{email}'"].post payload
315
- resp
316
- end
317
-
318
- # correspond on a ticket. Requires a hash, which must have an :id key
319
- # containing the ticket number. Returns the REST response. For a list of
320
- # fields you can use in correspondence, try "/opt/rt3/bin/rt correspond
321
- # ticket/1"
322
- #
323
- # rt.correspond( :id => id,
324
- # :Text => "We're sending help right away.",
325
- # :Attachment => "/tmp/admittance.doc" )
326
- def correspond(field_hash)
327
- if field_hash.has_key? :id
328
- if field_hash[:id] =~ /ticket\/(\d+)/
329
- id = $~[1]
330
- else
331
- id = field_hash[:id]
332
- end
333
- else
334
- raise "RT_Client.correspond requires a Ticket number in the 'id' key."
335
- end
336
- field_hash[:Action] = "correspond"
337
- payload = compose(field_hash)
338
- @site["ticket/#{id}/comment"].post payload
339
- end
340
-
341
- # Get a list of tickets matching some criteria.
342
- # Takes a string Ticket-SQL query and an optional "order by" parameter.
343
- # The order by is an RT field, prefix it with + for ascending
344
- # or - for descending.
345
- # Returns a nested array of arrays containing [ticket number, subject]
346
- # The outer array is in the order requested.
347
- #
348
- # hash = rt.list(:query => "Queue = 'Sales'")
349
- # hash = rt.list("Queue='Sales'")
350
- # hash = rt.list(:query => "Queue = 'Sales'", :order => "-Id")
351
- # hash = rt.list("Queue='Sales'","-Id")
352
- def list(*params)
353
- query = params[0]
354
- order = ""
355
- if params.size > 1
356
- order = params[1]
357
- end
358
- if params[0].class == Hash
359
- params = params[0]
360
- query = params[:query] if params.has_key? :query
361
- order = params[:order] if params.has_key? :order
362
- end
363
- reply = []
364
- resp = @site["search/ticket/?query=#{URI.escape(query)}&orderby=#{order}&format=s"].get
365
- raise "Invalid query (#{query})" if resp =~ /Invalid query/
366
- resp = resp.split("\n") # convert to array of lines
367
- resp.each do |line|
368
- f = line.match(/^(\d+):\s*(.*)/)
369
- reply.push [f[1],f[2]] if f.class == MatchData
370
- end
371
- reply
372
- end
373
-
374
- # A more extensive(expensive) query then the list method. Takes the same
375
- # parameters as the list method; a string Ticket-SQL query and optional
376
- # order, but returns a lot more information. Instead of just the ID and
377
- # subject, you get back an array of hashes, where each hash represents
378
- # one ticket, indentical to what you get from the show method (which only
379
- # acts on one ticket). Use with caution; this can take a long time to
380
- # execute.
381
- #
382
- # array = rt.query("Queue='Sales'")
383
- # array = rt.query(:query => "Queue='Sales'",:order => "+Id")
384
- # array = rt.query("Queue='Sales'","+Id")
385
- # => array[0] = { "id" => "123", "requestors" => "someone@..", etc etc }
386
- # => array[1] = { "id" => "126", "requestors" => "someone@else..", etc etc }
387
- # => array[0]["id"] = "123"
388
- def query(*params)
389
- query = params[0]
390
- order = ""
391
- if params.size > 1
392
- order = params[1]
393
- end
394
- if params[0].class == Hash
395
- params = params[0]
396
- query = params[:query] if params.has_key? :query
397
- order = params[:order] if params.has_key? :order
398
- end
399
- replies = []
400
- resp = @site["search/ticket/?query=#{URI.escape(query)}&orderby=#{order}&format=l"].get
401
- return replies if resp =~/No matching results./
402
- raise "Invalid query (#{query})" if resp =~ /Invalid query/
403
- resp.gsub!(/RT\/\d\.\d\.\d\s\d{3}\s.*\n\n/,"") # strip HTTP response
404
- tickets = resp.split("\n--\n") # -- occurs between each ticket
405
- tickets.each do |ticket|
406
- ticket.gsub!(/^\n/,"") # strip leading blank lines
407
- ticket.gsub!(/\n\n/,"\n") # remove blank lines for TMail
408
- th = TMail::Mail.parse(ticket)
409
- reply = {}
410
- th.each_header do |k,v|
411
- case k
412
- when 'created','due','told','lastupdated','started'
413
- begin
414
- vv = DateTime.parse(v.to_s)
415
- reply["#{k}"] = vv.strftime("%Y-%m-%d %H:%M:%S")
416
- rescue ArgumentError
417
- reply["#{k}"] = v.to_s
418
- end
419
- else
420
- reply["#{k}"] = v.to_s
421
- end
422
- end
423
- replies.push reply
424
- end
425
- replies
426
- end
427
-
428
- # Get a list of history transactions for a ticket. Takes a ticket ID and
429
- # an optional format parameter. If the format is ommitted, the short
430
- # format is assumed. If the short format is requested, it returns an
431
- # array of 2 element arrays, where each 2-element array is [ticket_id,
432
- # description]. If the long format is requested, it returns an array of
433
- # hashes, where each hash contains the keys:
434
- #
435
- # id:: (history-id)
436
- # Ticket:: (Ticket this history item belongs to)
437
- # TimeTaken:: (time entered by the actor that generated this item)
438
- # Type:: (what type of history item this is)
439
- # Field:: (what field is affected by this item, if any)
440
- # OldValue:: (the old value of the Field)
441
- # NewValue:: (the new value of the Field)
442
- # Data:: (Additional data about the item)
443
- # Description:: (Description of this item; same as short format)
444
- # Content:: (The content of this item)
445
- # Creator:: (the RT user that created this item)
446
- # Created:: (Date/time this item was created)
447
- # Attachments:: (a hash describing attachments to this item)
448
- #
449
- # history = rt.history(881)
450
- # history = rt.history(881,"short")
451
- # => [["10501"," Ticket created by blah"],["10510"," Comments added by userX"]]
452
- # history = rt.history(881,"long")
453
- # history = rt.history(:id => 881, :format => "long")
454
- # => [{"id" => "6171", "ticket" => "881" ....}, {"id" => "6180", ...} ]
455
- def history(*params)
456
- id = params[0]
457
- format = "short"
458
- format = params[1].downcase if params.size > 1
459
- comments = false
460
- comments = params[2] if params.size > 2
461
- if params[0].class == Hash
462
- params = params[0]
463
- id = params[:id] if params.has_key? :id
464
- format = params[:format].downcase if params.has_key? :format
465
- comments = params[:comments] if params.has_key? :comments
466
- end
467
- id = id.to_s
468
- id = $~[1] if id =~ /ticket\/(\d+)/
469
- resp = @site["ticket/#{id}/history?format=#{format[0,1]}"].get
470
- if format[0,1] == "s"
471
- if comments
472
- h = resp.split("\n").select{ |l| l =~ /^\d+:/ }
473
- else
474
- h = resp.split("\n").select{ |l| l =~ /^\d+: [^Comments]/ }
475
- end
476
- list = h.map { |l| l.split(":") }
477
- else
478
- resp.gsub!(/RT\/\d\.\d\.\d\s\d{3}\s.*\n\n/,"") # toss the HTTP response
479
- resp.gsub!(/^#.*?\n\n/,"") # toss the 'total" line
480
- resp.gsub!(/^\n/m,"") # toss blank lines
481
- items = resp.split("\n--\n")
482
- list = []
483
- items.each do |item|
484
- th = TMail::Mail.parse(item)
485
- next if not comments and th["type"].to_s =~ /Comment/ # skip comments
486
- reply = {}
487
- th.each_header do |k,v|
488
- attachments = []
489
- case k
490
- when "attachments"
491
- temp = item.match(/Attachments:\s*(.*)/m)
492
- if temp.class != NilClass
493
- atarr = temp[1].split("\n")
494
- atarr.map { |a| a.gsub!(/^\s*/,"") }
495
- atarr.each do |a|
496
- i = a.match(/(\d+):\s*(.*)/)
497
- s={}
498
- s[:id] = i[1].to_s
499
- s[:name] = i[2].to_s
500
- sz = i[2].match(/(.*?)\s*\((.*?)\)/)
501
- if sz.class == MatchData
502
- s[:name] = sz[1].to_s
503
- s[:size] = sz[2].to_s
504
- end
505
- attachments.push s
506
- end
507
- reply["attachments"] = attachments
508
- end
509
- when "content"
510
- reply["content"] = v.to_s
511
- temp = item.match(/^Content: (.*?)^\w+:/m) # TMail strips line breaks
512
- reply["content"] = temp[1] if temp.class != NilClass
513
- else
514
- reply["#{k}"] = v.to_s
515
- end
516
- end
517
- list.push reply
518
- end
519
- end
520
- list
521
- end
522
-
523
- # Get the detail for a single history item. Needs a ticket ID and a
524
- # history item ID, returns a hash of RT Fields => values. The hash
525
- # also contains a special key named "attachments", whose value is
526
- # an array of hashes, where each hash represents an attachment. The hash
527
- # keys are :id, :name, and :size.
528
- #
529
- # x = rt.history_item(21, 6692)
530
- # x = rt.history_item(:id => 21, :history => 6692)
531
- # => x = {"ticket" => "21", "creator" => "somebody", "description" =>
532
- # => "something happened", "attachments" => [{:name=>"file.txt",
533
- # => :id=>"3289", size=>"651b"}, {:name=>"another.doc"... }]}
534
- def history_item(*params)
535
- id = params[0]
536
- history = params[1]
537
- if params[0].class == Hash
538
- params = params[0]
539
- id = params[:id] if params.has_key? :id
540
- history = params[:history] if params.has_key? :history
541
- end
542
- reply = {}
543
- resp = @site["ticket/#{id}/history/id/#{history}"].get
544
- return reply if resp =~ /not related/ # history id must be related to the ticket id
545
- resp.gsub!(/RT\/\d\.\d\.\d\s\d{3}\s.*\n\n/,"") # toss the HTTP response
546
- resp.gsub!(/^#.*?\n\n/,"") # toss the 'total" line
547
- resp.gsub!(/^\n/m,"") # toss blank lines
548
- th = TMail::Mail.parse(resp)
549
- attachments = []
550
- th.each_header do |k,v|
551
- case k
552
- when "attachments"
553
- temp = resp.match(/Attachments:\s*(.*)[^\w|$]/m)
554
- if temp.class != NilClass
555
- atarr = temp[1].split("\n")
556
- atarr.map { |a| a.gsub!(/^\s*/,"") }
557
- atarr.each do |a|
558
- i = a.match(/(\d+):\s*(.*)/)
559
- s={}
560
- s[:id] = i[1]
561
- s[:name] = i[2]
562
- sz = i[2].match(/(.*?)\s*\((.*?)\)/)
563
- if sz.class == MatchData
564
- s[:name] = sz[1]
565
- s[:size] = sz[2]
566
- end
567
- attachments.push s
568
- end
569
- reply["#{k}"] = attachments
570
- end
571
- when "content"
572
- reply["content"] = v.to_s
573
- temp = resp.match(/^Content: (.*?)^\w+:/m) # TMail strips line breaks
574
- reply["content"] = temp[1] if temp.class != NilClass
575
- else
576
- reply["#{k}"] = v.to_s
577
- end
578
- end
579
- reply
580
- end
581
-
582
- # Get a list of attachments related to a ticket.
583
- # Requires a ticket id, returns an array of hashes where each hash
584
- # represents one attachment. Hash keys are :id, :name, :type, :size.
585
- # You can optionally request that unnamed attachments be included,
586
- # the default is to not include them.
587
- def attachments(*params)
588
- id = params[0]
589
- unnamed = params[1]
590
- if params[0].class == Hash
591
- params = params[0]
592
- id = params[:id] if params.has_key? :id
593
- unnamed = params[:unnamed] if params.has_key? :unnamed
594
- end
595
- unnamed = false if unnamed.to_s == "0"
596
- id = $~[1] if id =~ /ticket\/(\d+)/
597
- resp = @site["ticket/#{id}/attachments"].get
598
- resp.gsub!(/RT\/\d\.\d\.\d\s\d{3}\s.*\n\n/,"") # toss the HTTP response
599
- resp.gsub!(/^\n/m,"") # toss blank lines
600
- th = TMail::Mail.parse(resp)
601
- list = th["attachments"].to_s.split(",")
602
- attachments = []
603
- list.each do |v|
604
- attachment = {}
605
- m=v.match(/(\d+):\s+(.*?)\s+\((.*?)\s+\/\s+(.*?)\)/)
606
- if m.class == MatchData
607
- next if m[2] == "(Unnamed)" and !unnamed
608
- attachment[:id] = m[1]
609
- attachment[:name] = m[2]
610
- attachment[:type] = m[3]
611
- attachment[:size] = m[4]
612
- attachments.push attachment
613
- end
614
- end
615
- attachments
616
- end
617
-
618
- # Get attachment content for single attachment. Requires a ticket ID
619
- # and an attachment ID, which must be related. If a directory parameter
620
- # is supplied, the attachment is written to that directory. If not,
621
- # the attachment content is returned in the hash returned by the
622
- # function as the key 'content', along with some other keys you always get:
623
- #
624
- # transaction:: the transaction id
625
- # creator:: the user id number who attached it
626
- # id:: the attachment id
627
- # filename:: the name of the file
628
- # contenttype:: MIME content type of the attachment
629
- # created:: date of the attachment
630
- # parent:: an attachment id if this was an embedded MIME attachment
631
- #
632
- # x = get_attachment(21,3879)
633
- # x = get_attachment(:ticket => 21, :attachment => 3879)
634
- # x = get_attachment(:ticket => 21, :attachment => 3879, :dir = "/some/dir")
635
- def get_attachment(*params)
636
- tid = params[0]
637
- aid = params[1]
638
- dir = nil
639
- dir = params[2] if params.size > 2
640
- if params[0].class == Hash
641
- params = params[0]
642
- tid = params[:ticket] if params.has_key? :ticket
643
- aid = params[:attachment] if params.has_key? :attachment
644
- dir = params[:dir] if params.has_key? :dir
645
- end
646
- tid = $~[1] if tid =~ /ticket\/(\d+)/
647
- resp = @site["ticket/#{tid}/attachments/#{aid}"].get
648
- resp.gsub!(/RT\/\d\.\d\.\d\s\d{3}\s.*\n\n/,"") # toss HTTP response
649
- headers = TMail::Mail.parse(resp)
650
- reply = {}
651
- headers.each_header do |k,v|
652
- reply["#{k}"] = v.to_s
653
- end
654
- content = resp.match(/Content:\s+(.*)/m)[1]
655
- content.gsub!(/\n\s{9}/,"\n") # strip leading spaces on each line
656
- content.chomp!
657
- content.chomp!
658
- content.chomp! # 3 carriage returns at the end
659
- binary = Iconv.conv("ISO-8859-1","UTF-8",content) # convert encoding
660
- if dir
661
- fh = File.new("#{dir}/#{headers['Filename'].to_s}","wb")
662
- fh.write binary
663
- fh.close
664
- else
665
- reply["content"] = binary
666
- end
667
- reply
668
- end
669
-
670
- # Add a watcher to a ticket, but only if not already a watcher. Takes a
671
- # ticket ID, an email address (or array of email addresses), and an
672
- # optional watcher type. If no watcher type is specified, its assumed to
673
- # be "Cc". Possible watcher types are 'Requestors', 'Cc', and 'AdminCc'.
674
- #
675
- # rt.add_watcher(123,"someone@here.com")
676
- # rt.add_watcher(123,["someone@here.com","another@there.com"])
677
- # rt.add_watcher(123,"someone@here.com","Requestors")
678
- # rt.add_watcher(:id => 123, :addr => "someone@here.com")
679
- # rt.add_watcher(:id => 123, :addr => ["someone@here.com","another@there.com"])
680
- # rt.add_watcher(:id => 123, :addr => "someone@here.com", :type => "AdminCc")
681
- def add_watcher(*params)
682
- tid = params[0]
683
- addr = []
684
- type = "cc"
685
- addr = params[1] if params.size > 1
686
- type = params[2] if params.size > 2
687
- if params[0].class == Hash
688
- params = params[0]
689
- tid = params[:id] if params.has_key? :id
690
- addr = params[:addr] if params.has_key? :addr
691
- type = params[:type] if params.has_key? :type
692
- end
693
- addr = addr.to_a.uniq # make it array if its just a string, and remove dups
694
- type.downcase!
695
- tobj = show(tid) # get current watchers
696
- ccs = tobj["cc"].split(", ")
697
- accs = tobj["admincc"].split(", ")
698
- reqs = tobj["requestors"].split(", ")
699
- watchers = ccs | accs | reqs # union of all watchers
700
- addr.each do |e|
701
- case type
702
- when "cc"
703
- ccs.push(e) if not watchers.include?(e)
704
- when "admincc"
705
- accs.push(e) if not watchers.include?(e)
706
- when "requestors"
707
- reqs.push(e) if not watchers.include?(e)
708
- end
709
- end
710
- case type
711
- when "cc"
712
- edit(:id => tid, :Cc => ccs.join(","))
713
- when "admincc"
714
- edit(:id => tid, :AdminCc => accs.join(","))
715
- when "requestors"
716
- edit(:id => tid, :Requestors => reqs.join(","))
717
- end
718
- end
719
-
720
- # don't give up the password when the object is inspected
721
- def inspect # :nodoc:
722
- mystr = super()
723
- mystr.gsub!(/(.)pass=.*?([,\}])/,"\\1pass=<hidden>\\2")
724
- mystr
725
- end
726
-
727
- private
728
-
729
- # Private helper for composing RT's "forms". Requires a hash where the
730
- # keys are field names for an RT form. If there's a :Text key, the value
731
- # is modified to insert whitespace on continuation lines. If there's an
732
- # :Attachment key, the value is assumed to be a comma-separated list of
733
- # filenames to attach. It returns a multipart MIME body complete
734
- # with boundaries and headers, suitable for an HTTP POST.
735
- def compose(fields) # :doc:
736
- body = ""
737
- if fields.class != Hash
738
- raise "RT_Client.compose requires parameters as a hash."
739
- end
740
-
741
- # fixup Text field for RFC822 compliance
742
- if fields.has_key? :Text
743
- fields[:Text].gsub!(/\n/,"\n ") # insert a space on continuation lines.
744
- end
745
-
746
- # attachments
747
- if fields.has_key? :Attachments
748
- fields[:Attachment] = fields[:Attachments]
749
- fields.delete :Attachments
750
- end
751
- if fields.has_key? :Attachment
752
- filenames = fields[:Attachment].split(',')
753
- i = 0
754
- filenames.each do |v|
755
- filename = File.basename(v)
756
- mime_type = MIME::Types.type_for(v)[0]
757
- i += 1
758
- param_name = "attachment_#{i.to_s}"
759
- body << "--#{@boundary}\r\n"
760
- body << "Content-Disposition: form-data; "
761
- body << "name=\"#{URI.escape(param_name.to_s)}\"; "
762
- body << "filename=\"#{URI.escape(filename)}\"\r\n"
763
- body << "Content-Type: #{mime_type.simplified}\r\n\r\n"
764
- body << File.read(v) # oh dear, lets hope you have lots of RAM
765
- end
766
- # strip paths from filenames
767
- fields[:Attachment] = filenames.map {|f| File.basename(f)}.join(',')
768
- end
769
- field_array = fields.map { |k,v| "#{k}: #{v}" }
770
- content = field_array.join("\n") # our form
771
- # add the form to the end of any attachments
772
- body << "--#{@boundary}\r\n"
773
- body << "Content-Disposition: form-data; "
774
- body << "name=\"content\";\r\n\r\n"
775
- body << content << "\r\n"
776
- body << "--#{@boundary}--\r\n"
777
- body
778
- end
779
- end
data/rt/rtxmlsrv.rb DELETED
@@ -1,261 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- ## XML RPC service to provide a cross-platform API for
4
- ## RT ticket creation/maintenance. Essentially just a wrapper
5
- ## around the rt/client library.
6
-
7
- require "rubygems" # so we can load gems
8
- require "rt/client" # rt-client REST library
9
- require "xmlrpc/server" # that's what we're doing
10
- require "date" # for parsing arbitrary date formats
11
- require "pp"
12
-
13
- PORT=8080
14
- MAX_CONN=50
15
-
16
- # extend the Hash class to
17
- # translate string keys into symbol keys
18
- class Hash # :nodoc:
19
- def remapkeys!
20
- n = Hash.new
21
- self.each_key do |key|
22
- if key =~ /\{/
23
- n[eval(":\"#{key}\"")] = self[key]
24
- else
25
- n[eval(":#{key}")] = self[key]
26
- end
27
- end
28
- self.replace(n)
29
- n = nil
30
- self
31
- end
32
- end
33
-
34
- class TicketSrv
35
-
36
- def initialize
37
- end
38
-
39
- INTERFACE = XMLRPC::interface("rt") {
40
- meth 'string add_watcher(struct)','Calls RT_Client::add_watcher'
41
- meth 'array attachments(struct)','Calls RT_Client::attachments'
42
- meth 'string comment(struct)','Calls RT_Client::comment'
43
- meth 'string correspond(struct)','Calls RT_Client::correspond'
44
- meth 'string create(struct)','Calls RT_Client::create'
45
- meth 'string create_user(struct)','Calls RT_Client::create_user'
46
- meth 'string edit(struct)','Calls RT_Client::edit'
47
- meth 'string edit_or_create_user(struct)','Calls RT_Client::edit_or_create_user'
48
- meth 'struct get_attachment(struct)','Calls RT_Client::get_attachment'
49
- meth 'struct history(struct)','Calls RT_Client::history (long form)'
50
- meth 'struct history_item(struct)','Calls RT_Client::history_item'
51
- meth 'array list(struct)','Calls RT_Client::list'
52
- meth 'array query(struct)','Calls RT_Client::query (long form)'
53
- meth 'struct show(struct)','Calls RT_Client::show'
54
- }
55
-
56
- # Allows watchers to be added via RT_Client::add_watcher
57
- # You need to pass :id, :addr, and optionally :type
58
- def add_watcher(struct)
59
- struct.remapkeys!
60
- if struct.has_key? :user and struct.has_key? :pass
61
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
62
- else
63
- rt = RT_Client.new
64
- end
65
- val = rt.add_watcher(struct)
66
- rt = nil
67
- val
68
- end
69
-
70
- # Gets a list of attachments via RT_Client::attachments
71
- # You need to pass :id, and optionally :unnamed
72
- def attachments(struct)
73
- struct.remapkeys!
74
- if struct.has_key? :user and struct.has_key? :pass
75
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
76
- else
77
- rt = RT_Client.new
78
- end
79
- rt = RT_Client.new
80
- val = rt.attachments(struct)
81
- rt = nil
82
- val
83
- end
84
-
85
- # Adds comments to tickets via RT_Client::comment
86
- def comment(struct)
87
- struct.remapkeys!
88
- if struct.has_key? :user and struct.has_key? :pass
89
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
90
- else
91
- rt = RT_Client.new
92
- end
93
- val = rt.comment(struct)
94
- rt = nil
95
- val
96
- end
97
-
98
- # Allows new tickets to be created via RT_Client::correspond
99
- def correspond(struct)
100
- struct.remapkeys!
101
- if struct.has_key? :user and struct.has_key? :pass
102
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
103
- else
104
- rt = RT_Client.new
105
- end
106
- val = rt.correspond(struct)
107
- rt = nil
108
- val
109
- end
110
-
111
- # Allows new tickets to be created via RT_Client::create
112
- def create(struct)
113
- struct.remapkeys!
114
- if struct.has_key? :user and struct.has_key? :pass
115
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
116
- else
117
- rt = RT_Client.new
118
- end
119
- val = rt.create(struct)
120
- rt = nil
121
- val
122
- end
123
-
124
- # Allows new users to be created via RT_Client::create_user
125
- def create_user(struct)
126
- struct.remapkeys!
127
- if struct.has_key? :user and struct.has_key? :pass
128
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
129
- else
130
- rt = RT_Client.new
131
- end
132
- val = rt.create_user(struct)
133
- rt = nil
134
- val
135
- end
136
-
137
- # Allows new users to be edited or created if they don't exist
138
- def edit_or_create_user(struct)
139
- struct.remapkeys!
140
- if struct.has_key? :user and struct.has_key? :pass
141
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
142
- else
143
- rt = RT_Client.new
144
- end
145
- val = rt.edit_or_create_user(struct)
146
- rt = nil
147
- val
148
- end
149
-
150
- # Allows existing ticket to be modified via RT_Client::edit
151
- def edit(struct)
152
- struct.remapkeys!
153
- if struct.has_key? :user and struct.has_key? :pass
154
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
155
- else
156
- rt = RT_Client.new
157
- end
158
- val = rt.edit(struct)
159
- rt = nil
160
- val
161
- end
162
-
163
- # Retrieves attachments via RT_Client::get_attachment
164
- def get_attachment(struct)
165
- struct.remapkeys!
166
- if struct.has_key? :user and struct.has_key? :pass
167
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
168
- else
169
- rt = RT_Client.new
170
- end
171
- val = rt.get_attachment(struct)
172
- rt = nil
173
- val
174
- end
175
-
176
- # Gets the history of a ticket via RT_Client::history
177
- def history(struct)
178
- struct.remapkeys!
179
- if struct.has_key? :user and struct.has_key? :pass
180
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
181
- else
182
- rt = RT_Client.new
183
- end
184
- val = rt.history(struct)
185
- rt = nil
186
- val
187
- end
188
-
189
- # Gets a single history item via RT_Client::history_item
190
- def history_item(struct)
191
- struct.remapkeys!
192
- if struct.has_key? :user and struct.has_key? :pass
193
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
194
- else
195
- rt = RT_Client.new
196
- end
197
- val = rt.history_item(struct)
198
- rt = nil
199
- val
200
- end
201
-
202
- # Gets a list of tickets via RT_Client::list
203
- def list(struct)
204
- struct.remapkeys!
205
- if struct.has_key? :user and struct.has_key? :pass
206
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
207
- else
208
- rt = RT_Client.new
209
- end
210
- val = rt.list(struct)
211
- rt = nil
212
- val
213
- end
214
-
215
- # Gets a list of tickets via RT_Client::query
216
- def query(struct)
217
- struct.remapkeys!
218
- if struct.has_key? :user and struct.has_key? :pass
219
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
220
- else
221
- rt = RT_Client.new
222
- end
223
- val = rt.query(struct)
224
- rt = nil
225
- val
226
- end
227
-
228
- # Gets detail (minus history/attachments) via RT_Client::show
229
- def show(struct)
230
- struct.remapkeys!
231
- if struct.has_key? :user and struct.has_key? :pass
232
- rt = RT_Client.new(:user => struct[:user], :pass => struct[:pass])
233
- else
234
- rt = RT_Client.new
235
- end
236
- val = rt.show(struct)
237
- rt = nil
238
- val
239
- end
240
-
241
- end # class TicketSrv
242
-
243
- pid = fork do
244
- Signal.trap('HUP','IGNORE')
245
- # set up a log file
246
- logfile = File.dirname(__FILE__) + "/ticketsrv.log"
247
- accfile = File.dirname(__FILE__) + "/access.log"
248
- acc = File.open(accfile,"a+")
249
- $stderr.reopen acc # redirect $stderr to the log as well
250
- # determine the IP address to listen on and create the server
251
- sock = Socket.getaddrinfo(Socket.gethostname,PORT,Socket::AF_INET,Socket::SOCK_STREAM)
252
- $s = XMLRPC::Server.new(sock[0][1], sock[0][3], MAX_CONN, logfile)
253
- $s.set_parser(XMLRPC::XMLParser::XMLStreamParser.new)
254
- $s.add_handler(TicketSrv::INTERFACE, TicketSrv.new)
255
- $s.add_introspection
256
- $s.serve # start serving
257
- $stderr.reopen STDERR
258
- acc.close
259
- end
260
- Process.detach(pid)
261
-