ruby-bugzilla 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/bugzilla.rb ADDED
@@ -0,0 +1,787 @@
1
+ # bugzilla.rb
2
+ # Copyright (C) 2010 Red Hat, Inc.
3
+ #
4
+ # Authors:
5
+ # Akira TAGOH <tagoh@redhat.com>
6
+ #
7
+ # This program is free software; you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation; either version 2 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software
19
+ # Foundation, Inc., 59 Temple Place - Suite 330,
20
+ # Boston, MA 02111-1307, USA.
21
+
22
+ require 'xmlrpc/client'
23
+
24
+
25
+ =begin rdoc
26
+
27
+ == Bugzilla
28
+
29
+ =end
30
+
31
+ module Bugzilla
32
+
33
+ VERSION = "0.1"
34
+
35
+ =begin rdoc
36
+
37
+ === Bugzilla::Plugin
38
+
39
+ =end
40
+
41
+ module Plugin
42
+
43
+ =begin rdoc
44
+
45
+ ==== Bugzilla::Plugin::Template
46
+
47
+ =end
48
+
49
+ class Template
50
+ @@plugins = []
51
+
52
+ def initialize
53
+ @hostname = nil
54
+ end # def initialize
55
+
56
+ attr_reader :hostname
57
+
58
+ def Template.inherited(subclass)
59
+ @@plugins << subclass
60
+ end # def inherited
61
+
62
+ def run(hook, host, *args)
63
+ @@plugins.each do |k|
64
+ i = k.new
65
+ if i.hostname == host || host.nil? then
66
+ case hook
67
+ when :parser
68
+ i.parserhook(*args)
69
+ when :pre
70
+ i.prehook(*args)
71
+ when :post
72
+ i.posthook(*args)
73
+ else
74
+ end
75
+ end
76
+ end
77
+ end # def run
78
+
79
+ def parserhook(parser, argv, opts)
80
+ end # def parserhook
81
+
82
+ def prehook(cmd, opts)
83
+ end # def prehook
84
+
85
+ def posthook(cmd, opts)
86
+ end # def posthook
87
+
88
+ end # class Template
89
+
90
+ end # module Plugin
91
+
92
+ =begin rdoc
93
+
94
+ === Bugzilla::XMLRPC
95
+
96
+ =end
97
+
98
+ class XMLRPC
99
+
100
+ =begin rdoc
101
+
102
+ ==== Bugzilla::XMLRPC#new(server, port = 443)
103
+
104
+ =end
105
+
106
+ def initialize(server, port = 443)
107
+ use_ssl = port == 443 ? true : false
108
+ @xmlrpc = ::XMLRPC::Client.new(server, '/xmlrpc.cgi', port, nil, nil, nil, nil, use_ssl, 60)
109
+ end # def initialize
110
+
111
+ =begin rdoc
112
+
113
+ ==== Bugzilla::XMLRPC#call(cmd, params, user = nil, password = nil)
114
+
115
+ =end
116
+
117
+ def call(cmd, params = {}, user = nil, password = nil)
118
+ params = {} if params.nil?
119
+ params['Bugzilla_login'] = user unless user.nil? || password.nil?
120
+ params['Bugzilla_password'] = password unless user.nil? || password.nil?
121
+ @xmlrpc.call(cmd, params)
122
+ end # def call
123
+
124
+ end # class XMLRPC
125
+
126
+ =begin rdoc
127
+
128
+ === Bugzilla::Skeleton
129
+
130
+ =end
131
+
132
+ class Skeleton
133
+
134
+ def initialize(iface)
135
+ @iface = iface
136
+ end # def initialize
137
+
138
+ def method_missing(symbol, *args)
139
+ m = "_#{symbol}"
140
+ klass = self.class.to_s.sub(/\ABugzilla::/, '')
141
+ fm = "#{klass}.#{symbol}"
142
+ if self.respond_to?(m) then
143
+ __send__(m, fm, *args)
144
+ else
145
+ raise NoMethodError, sprintf("No such Bugzilla APIs: %s.%s", klass, symbol)
146
+ end
147
+ end # def method_missing
148
+
149
+ end # class Skeleton
150
+
151
+ =begin rdoc
152
+
153
+ === Bugzilla::Bugzilla
154
+
155
+ Bugzilla::Bugzilla class is to access the
156
+ Bugzilla::WebService::Bugzilla API that provides functions
157
+ tell you about Bugzilla in general.
158
+
159
+ =end
160
+
161
+ class Bugzilla < Skeleton
162
+
163
+ =begin rdoc
164
+
165
+ ==== Bugzilla::Bugzilla#check_version(version_)
166
+
167
+ Returns Array contains the result of the version check and
168
+ Bugzilla version that is running on.
169
+
170
+ =end
171
+
172
+ def check_version(version_)
173
+ v = version
174
+ f = false
175
+ if v.kind_of?(Hash) && v.include?("version") &&
176
+ v['version'] >= "#{version_}" then
177
+ f = true
178
+ end
179
+
180
+ [f, v['version']]
181
+ end # def check_version
182
+
183
+ =begin rdoc
184
+
185
+ ==== Bugzilla::Bugzilla#requires_version(cmd, version_)
186
+
187
+ Raise an exception if the Bugzilla doesn't satisfy
188
+ the requirement of the _version_.
189
+
190
+ =end
191
+
192
+ def requires_version(cmd, version_)
193
+ v = check_version(version_)
194
+ raise NoMethodError, sprintf("%s is not supported in Bugzilla %s", cmd, v[1]) unless v[0]
195
+ end # def requires_version
196
+
197
+ =begin rdoc
198
+
199
+ ==== Bugzilla::Bugzilla#version
200
+
201
+ Raw Bugzilla API to obtain the Bugzilla version.
202
+
203
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bugzilla.html
204
+
205
+ =end
206
+
207
+ =begin rdoc
208
+
209
+ ==== Bugzilla::Bugzilla#extensions
210
+
211
+ Raw Bugzilla API to obtain the information about
212
+ the extensions that are currently installed and enabled in
213
+ the Bugzilla.
214
+
215
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bugzilla.html
216
+
217
+ =end
218
+
219
+ =begin rdoc
220
+
221
+ ==== Bugzilla::Bugzilla#timezone
222
+
223
+ Raw Bugzilla API to obtain the timezone that Bugzilla
224
+ expects dates and times in.
225
+
226
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bugzilla.html
227
+
228
+ =end
229
+
230
+ =begin rdoc
231
+
232
+ ==== Bugzilla::Bugzilla#time
233
+
234
+ Raw Bugzilla API to obtain the information about what time
235
+ the bugzilla server thinks it is, and what timezone it's
236
+ running on.
237
+
238
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bugzilla.html
239
+
240
+ =end
241
+
242
+ protected
243
+
244
+ def _version(cmd, *args)
245
+ @iface.call(cmd)
246
+ end # def _version
247
+
248
+ def _extensions(cmd, *args)
249
+ requires_version(cmd, 3.2)
250
+
251
+ @iface.call(cmd)
252
+ end # def _extensions
253
+
254
+ def _timezone(cmd, *args)
255
+ @iface.call(cmd)
256
+ end # def _timezone
257
+
258
+ def _time(cmd, *args)
259
+ requires_version(cmd, 3.4)
260
+
261
+ @iface.call(cmd)
262
+ end # def _time
263
+
264
+ end # class Bugzilla
265
+
266
+ =begin rdoc
267
+
268
+ === Bugzilla::APITemplate
269
+
270
+ =end
271
+
272
+ class APITemplate < Skeleton
273
+
274
+ def initialize(iface)
275
+ super
276
+
277
+ @bz = Bugzilla.new(iface)
278
+ end # def initialize
279
+
280
+ def method_missing(symbol, *args)
281
+ if @bz.respond_to?(symbol) then
282
+ @bz.__send__(symbol, *args)
283
+ else
284
+ super
285
+ end
286
+ end # def method_missing
287
+
288
+ end # class APITemplate
289
+
290
+ =begin rdoc
291
+
292
+ === Bugzilla::Product
293
+
294
+ Bugzilla::Product class is to access
295
+ the Bugzilla::WebService::Product API that allows you to
296
+ list the available Products and get information about them.
297
+
298
+ =end
299
+
300
+ class Product < APITemplate
301
+
302
+ =begin rdoc
303
+
304
+ ==== Bugzilla::Product#selectable_products
305
+
306
+ Returns the products that the user can search on as Hash
307
+ contains the product name as the Hash key and Array as the
308
+ value. Array contains the list of _id_, _name_,
309
+ _description_ and _internals_ according to API documentation
310
+ though, actually the component, the version and the target
311
+ milestone.
312
+
313
+ =end
314
+
315
+ def selectable_products
316
+ ids = get_selectable_products
317
+ get(ids)
318
+ end # def selectable_products
319
+
320
+ =begin rdoc
321
+
322
+ ==== Bugzilla::Product#enterable_products
323
+
324
+ Returns the products that the user can enter bugs against
325
+ as Hash contains the product name as the Hash key and Array
326
+ as the value. Array contains the list of _id_, _name_,
327
+ _description_ and _internals_ according to API documentation
328
+ though, actually the component, the version and the target
329
+ milestone.
330
+
331
+ =end
332
+
333
+ def enterable_products
334
+ ids = get_enterable_products
335
+ get(ids)
336
+ end # def enterable_products
337
+
338
+ =begin rdoc
339
+
340
+ ==== Bugzilla::Product#accessible_products
341
+
342
+ Returns the products that the user can search or enter bugs
343
+ against as Hash contains the product name as the Hash key
344
+ and Array as the value. Array contains the list of _id_,
345
+ _name_, _description_ and _internals_ according to API
346
+ documentation though, actually the component, the version
347
+ and the target milestone.
348
+
349
+ =end
350
+
351
+ def accessible_products
352
+ ids = get_accessible_products
353
+ get(ids)
354
+ end # def accessible_products
355
+
356
+ =begin rdoc
357
+
358
+ ==== Bugzilla::Product#get_selectable_products
359
+
360
+ Raw Bugzilla API to obtain the products that the user can
361
+ search on.
362
+
363
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Product.html
364
+
365
+ =end
366
+
367
+ =begin rdoc
368
+
369
+ ==== Bugzilla::Product#get_enterable_products
370
+
371
+ Raw Bugzilla API to obtain the products that the user can
372
+ enter bugs against.
373
+
374
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Product.html
375
+
376
+ =end
377
+
378
+ =begin rdoc
379
+
380
+ ==== Bugzilla::Product#get_accessible_products
381
+
382
+ Raw Bugzilla API to obtain the products that the user can
383
+ search or enter bugs against.
384
+
385
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Product.html
386
+
387
+ =end
388
+
389
+ protected
390
+
391
+ def _get_selectable_products(cmd, *args)
392
+ @iface.call(cmd)
393
+ end # def _get_selectable_products
394
+
395
+ def _get_enterable_products(cmd, *args)
396
+ @iface.call(cmd)
397
+ end # def _get_entrable_products
398
+
399
+ def _get_accessible_products(cmd, *args)
400
+ @iface.call(cmd)
401
+ end # def _get_accessible_products
402
+
403
+ def _get(cmd, ids, *args)
404
+ params = {}
405
+
406
+ if ids.kind_of?(Hash) then
407
+ raise ArgumentError, sprintf("Invalid parameter: %s", ids.inspect) unless ids.include?('ids')
408
+ params[:ids] = ids['ids']
409
+ elsif ids.kind_of?(Array) then
410
+ params[:ids] = ids
411
+ else
412
+ params[:ids] = [ids]
413
+ end
414
+
415
+ @iface.call(cmd, params)
416
+ end # def _get
417
+
418
+ end # class Product
419
+
420
+ =begin rdoc
421
+
422
+ === Bugzilla::Bug
423
+
424
+ Bugzilla::Bug class is to access
425
+ the Bugzilla::WebService::Bug API that allows you to file
426
+ a new bug in Bugzilla or get information about bugs that
427
+ have already been filed.
428
+
429
+ =end
430
+
431
+ class Bug < APITemplate
432
+
433
+ FIELDS_SUMMARY = ["id", "product", "component", "status", "severity", "summary"]
434
+ FIELDS_DETAILS = FIELDS_SUMMARY + ["assigned_to", "internals", "priority", "resolution"]
435
+ FIELDS_ALL = ["alias", "assigned_to", "component",
436
+ "creation_time", "dupe_of",
437
+ "external_bugs", "groups", "id",
438
+ "internals", "is_open",
439
+ "last_change_time", "priority", "product",
440
+ "resolution", "severity", "status",
441
+ "summary"]
442
+
443
+ =begin rdoc
444
+
445
+ ==== Bugzilla::Bug#get_bugs(bugs, fields = Bugzilla::Bug::FIELDS_SUMMARY)
446
+
447
+ Get the _bugs_ information from Bugzilla. either of String
448
+ or Numeric or Array would be acceptable for _bugs_. you can
449
+ specify the fields you want to look up with _fields_.
450
+
451
+ FWIW this name conflicts to Bugzilla API but this isn's a
452
+ primitive method since get_bugs method in WebService API is
453
+ actually deprecated.
454
+
455
+ =end
456
+
457
+ def get_bugs(bugs, fields = ::Bugzilla::Bug::FIELDS_SUMMARY)
458
+ params = {}
459
+
460
+ if bugs.kind_of?(Array) then
461
+ params['ids'] = bugs
462
+ elsif bugs.kind_of?(Integer) ||
463
+ bugs.kind_of?(String) then
464
+ params['ids'] = [bugs]
465
+ else
466
+ raise ArgumentError, sprintf("Unknown type of arguments: %s", bugs.class)
467
+ end
468
+ unless fields.nil? then
469
+ unless (fields - ::Bugzilla::Bug::FIELDS_ALL).empty? then
470
+ raise ArgumentError, sprintf("Invalid fields: %s", (::Bugzilla::Bug::FIELDS_ALL - fields).join(' '))
471
+ end
472
+ params['include_fields'] = fields
473
+ end
474
+
475
+ result = get(params)
476
+
477
+ if fields.nil? || fields == ::Bugzilla::Bug::FIELDS_ALL then
478
+ get_comments(bugs).each do |id, c|
479
+ result['bugs'].each do |r|
480
+ if r['id'].to_s == id then
481
+ r['comments'] = c['comments']
482
+ r['comments'] = [] if r['comments'].nil?
483
+ break
484
+ end
485
+ end
486
+ end
487
+ end
488
+
489
+ # 'bugs' is only in interests.
490
+ # XXX: need to deal with 'faults' ?
491
+ result['bugs']
492
+ end # def get_bugs
493
+
494
+ =begin rdoc
495
+
496
+ ==== Bugzilla::Bug#get_comments(bugs)
497
+
498
+ =end
499
+
500
+ def get_comments(bugs)
501
+ params = {}
502
+
503
+ if bugs.kind_of?(Array) then
504
+ params['ids'] = bugs
505
+ elsif bugs.kind_of?(Integer) ||
506
+ bugs.kind_of?(String) then
507
+ params['ids'] = [bugs]
508
+ else
509
+ raise ArgumentError, sprintf("Unknown type of arguments: %s", bugs.class)
510
+ end
511
+
512
+ result = comments(params)
513
+
514
+ # not supporting comment_ids. so drop "comments".
515
+ result['bugs']
516
+ end # def get_comments
517
+
518
+ =begin rdoc
519
+
520
+ ==== Bugzilla::Bug#fields(params)
521
+
522
+ Raw Bugzilla API to obtain the information about valid bug
523
+ fields, including the lists of legal values for each field.
524
+
525
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bug.html
526
+
527
+ =end
528
+
529
+ =begin rdoc
530
+
531
+ ==== Bugzilla::Bug#legal_values(params)
532
+
533
+ Raw Bugzilla API to obtain the information what values are
534
+ allowed for a particular field.
535
+
536
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bug.html
537
+
538
+ =end
539
+
540
+ =begin rdoc
541
+
542
+ ==== Bugzilla::Bug#attachments(params)
543
+
544
+ Raw Bugzilla API to obtain the information about
545
+ attachments, given a list of bugs and/or attachment ids.
546
+
547
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bug.html
548
+
549
+ =end
550
+
551
+ =begin rdoc
552
+
553
+ ==== Bugzilla::Bug#comments(params)
554
+
555
+ Raw Bugzilla API to obtain the information about comments,
556
+ given a list of bugs and/or comment ids.
557
+
558
+ See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bug.html
559
+
560
+ =end
561
+
562
+ =begin rdoc
563
+
564
+ ==== Bugzilla::Bug#get(params)
565
+
566
+ Raw Bugzilla API to obtain the information about particular
567
+ bugs in the database.
568
+
569
+ =end
570
+
571
+ =begin rdoc
572
+
573
+ ==== Bugzilla::Bug#history(params)
574
+
575
+ Raw Bugzilla API to obtain the history of changes for
576
+ particular bugs in the database.
577
+
578
+ =end
579
+
580
+ =begin rdoc
581
+
582
+ ==== Bugzilla::Bug#search(params)
583
+
584
+ Raw Bugzilla API to search for bugs based on particular
585
+ criteria.
586
+
587
+ =end
588
+
589
+ protected
590
+
591
+ def _fields(cmd, *args)
592
+ requires_version(cmd, 3.6)
593
+ params = {}
594
+
595
+ if args[0].kind_of?(Array) then
596
+ x = args[0].map {|x| x.kind_of?(Integer)}.uniq
597
+ if x.length == 1 && x[0] then
598
+ params['ids'] = args[0]
599
+ else
600
+ x = args[0].map {|x| x.kind_of?(String)}.uniq
601
+ if x.length == 1 && x[0] then
602
+ params['names'] = args[0]
603
+ end
604
+ end
605
+ elsif args[0].kind_of?(Hash) then
606
+ params = args[0]
607
+ elsif args[0].kind_of?(Integer) then
608
+ params['ids'] = [args[0]]
609
+ elsif args[0].kind_of?(String) then
610
+ params['names'] = [args[0]]
611
+ elsif args[0].nil? then
612
+ else
613
+ raise ArgumentError, "Invalid parameters"
614
+ end
615
+
616
+ @iface.call(cmd, params)
617
+ end # def _fields
618
+
619
+ def _legal_values(cmd, *args)
620
+ raise ArgumentError, "Invalid parameters" unless args[0].kind_of?(Hash)
621
+
622
+ @iface.call(cmd, args[0])
623
+ end # def _legal_values
624
+
625
+ def _attachments(cmd, *args)
626
+ requires_version(cmd, 3.6)
627
+
628
+ raise ArgumentError, "Invalid parameters" unless args[0].kind_of?(Hash)
629
+
630
+ @iface.call(cmd, args[0])
631
+ end # def _attachments
632
+
633
+ def _comments(cmd, *args)
634
+ requires_version(cmd, 3.4)
635
+
636
+ raise ArgumentError, "Invalid parameters" unless args[0].kind_of?(Hash)
637
+
638
+ @iface.call(cmd, args[0])
639
+ end # def _comments
640
+
641
+ def _get(cmd, *args)
642
+ params = {}
643
+
644
+ if args[0].kind_of?(Hash) then
645
+ params = args[0]
646
+ elsif args[0].kind_of?(Array) then
647
+ params['ids'] = args[0]
648
+ elsif args[0].kind_of?(Integer) ||
649
+ args[0].kind_of?(String) then
650
+ params['ids'] = [args[0]]
651
+ else
652
+ raise ArgumentError, "Invalid parameters"
653
+ end
654
+ if check_version(3.4)[0] then
655
+ params['permissive'] = true
656
+ end
657
+
658
+ @iface.call(cmd, params)
659
+ end # def _get
660
+
661
+ def _history(cmd, *args)
662
+ requires_version(cmd, 3.4)
663
+
664
+ params = {}
665
+
666
+ if args[0].kind_of?(Hash) then
667
+ params = args[0]
668
+ elsif args[0].kind_of?(Array) then
669
+ params['ids'] = args[0]
670
+ elsif args[0].kind_of?(Integer) ||
671
+ args[0].kind_of?(String) then
672
+ params['ids'] = [args[0]]
673
+ else
674
+ raise ArgumentError, "Invalid parameters"
675
+ end
676
+
677
+ @iface.call(cmd, params)
678
+ end # def _history
679
+
680
+ def _search(cmd, *args)
681
+ requires_version(cmd, 3.4)
682
+
683
+ raise ArgumentError, "Invalid parameters" unless args[0].kind_of?(Hash)
684
+
685
+ @iface.call(cmd, args[0])
686
+ end # def _search
687
+
688
+ def __create(cmd, *args)
689
+ # FIXME
690
+ end # def _create
691
+
692
+ def __add_attachment(cmd, *args)
693
+ requires_version(cmd, 4.0)
694
+ # FIXME
695
+ end # def _add_attachment
696
+
697
+ def __add_comment(cmd, *args)
698
+ requires_version(cmd, 3.2)
699
+ # FIXME
700
+ end # def _add_comment
701
+
702
+ def __update(cmd, *args)
703
+ requires_version(cmd, 4.0)
704
+ # FIXME
705
+ end # def _update
706
+
707
+ def __update_see_also(cmd, *args)
708
+ requires_version(cmd, 3.4)
709
+ # FIXME
710
+ end # def _update_see_also
711
+
712
+ end # class Bug
713
+
714
+ =begin rdoc
715
+
716
+ === Bugzilla::User
717
+
718
+ Bugzilla::User class is to access the
719
+ Bugzilla::WebService::User API that allows you to create
720
+ User Accounts and log in/out using an existing account.
721
+
722
+ =end
723
+
724
+ class User < APITemplate
725
+
726
+ =begin rdoc
727
+
728
+ ==== Bugzilla::User#session(user, password)
729
+
730
+ Keeps the bugzilla session during doing something in the block.
731
+
732
+ =end
733
+
734
+ def session(user, password)
735
+ if user.nil? || password.nil? then
736
+ yield
737
+ else
738
+ login({'login'=>user, 'password'=>password, 'remember'=>true})
739
+ yield
740
+ logout
741
+ end
742
+ end # def session
743
+
744
+ =begin rdoc
745
+
746
+ ==== Bugzilla::User#login(params)
747
+
748
+ Raw Bugzilla API to log into Bugzilla.
749
+
750
+ =end
751
+
752
+ =begin rdoc
753
+
754
+ ==== Bugzilla::User#logout
755
+
756
+ Raw Bugzilla API to log out the user.
757
+
758
+ =end
759
+
760
+ protected
761
+
762
+ def _login(cmd, *args)
763
+ raise ArgumentError, "Invalid parameters" unless args[0].kind_of?(Hash)
764
+
765
+ @iface.call(cmd,args[0])
766
+ end # def _login
767
+
768
+ def _logout(cmd, *args)
769
+ @iface.call(cmd)
770
+ end # def _logout
771
+
772
+ def __offer_account_by_email(cmd, *args)
773
+ # FIXME
774
+ end # def _offer_account_by_email
775
+
776
+ def __create(cmd, *args)
777
+ # FIXME
778
+ end # def _create
779
+
780
+ def __get(cmd, *args)
781
+ requires_version(cmd, 3.4)
782
+ # FIXME
783
+ end # def _get
784
+
785
+ end # class User
786
+
787
+ end # module Bugzilla