1#!/usr/local/bin/perl 2# This Source Code Form is subject to the terms of the Mozilla Public 3# License, v. 2.0. If a copy of the MPL was not distributed with this 4# file, You can obtain one at http://mozilla.org/MPL/2.0/. 5# 6# This Source Code Form is "Incompatible With Secondary Licenses", as 7# defined by the Mozilla Public License, v. 2.0. 8 9=head1 NAME 10 11bz_webservice_demo.pl - Show how to talk to Bugzilla via XMLRPC 12 13=head1 SYNOPSIS 14 15C<bz_webservice_demo.pl [options]> 16 17C<bz_webservice_demo.pl --help> for detailed help 18 19=cut 20 21use 5.10.1; 22use strict; 23use warnings; 24 25use lib qw(lib); 26use Getopt::Long; 27use Pod::Usage; 28use File::Basename qw(dirname); 29use File::Spec; 30use XMLRPC::Lite; 31 32# If you want, say “use Bugzilla::WebService::Constants” here to get access 33# to Bugzilla's web service error code constants. 34# If you do this, remember to issue a “use lib” pointing to your Bugzilla 35# installation directory, too. 36 37my $help; 38my $Bugzilla_uri; 39my $Bugzilla_login; 40my $Bugzilla_password; 41my $Bugzilla_restrict; 42my $Bugzilla_token; 43my $bug_id; 44my $product_name; 45my $create_file_name; 46my $legal_field_values; 47my $add_comment; 48my $private; 49my $work_time; 50my $fetch_extension_info = 0; 51my $debug; 52 53GetOptions('help|h|?' => \$help, 54 'uri=s' => \$Bugzilla_uri, 55 'login:s' => \$Bugzilla_login, 56 'password=s' => \$Bugzilla_password, 57 'restrictlogin!' => \$Bugzilla_restrict, 58 'bug_id:s' => \$bug_id, 59 'product_name:s' => \$product_name, 60 'create:s' => \$create_file_name, 61 'field:s' => \$legal_field_values, 62 'comment:s' => \$add_comment, 63 'private:i' => \$private, 64 'worktime:f' => \$work_time, 65 'extension_info' => \$fetch_extension_info, 66 'debug' => \$debug 67 ) or pod2usage({'-verbose' => 0, '-exitval' => 1}); 68 69=head1 OPTIONS 70 71=over 72 73=item --help, -h, -? 74 75Print a short help message and exit. 76 77=item --uri 78 79URI to Bugzilla's C<xmlrpc.cgi> script, along the lines of 80C<http://your.bugzilla.installation/path/to/bugzilla/xmlrpc.cgi>. 81 82=item --login 83 84Bugzilla login name. Specify this together with B<--password> in order to log in. 85 86Specify this without a value in order to log out. 87 88=item --password 89 90Bugzilla password. Specify this together with B<--login> in order to log in. 91 92=item --restrictlogin 93 94Gives access to Bugzilla's "Bugzilla_restrictlogin" option. 95Specify this option while logging in to restrict the login token to be 96only valid from the IP address which called 97Don't specify this option to do the same thing as unchecking the box. 98 99See Bugzilla's restrictlogin parameter for details. 100 101=item --bug_id 102 103Pass a bug ID to have C<bz_webservice_demo.pl> do some bug-related test calls. 104 105=item --product_name 106 107Pass a product name to have C<bz_webservice_demo.pl> do some product-related 108test calls. 109 110=item --create 111 112Specify a file that contains settings for the creating of a new bug. 113 114=item --field 115 116Pass a field name to get legal values for this field. It must be either a 117global select field (such as bug_status, resolution, rep_platform, op_sys, 118priority, bug_severity) or a custom select field. 119 120=item --comment 121 122A comment to add to a bug identified by B<--bug_id>. You must also pass a B<--login> 123and B<--password> to log in to Bugzilla. 124 125=item --private 126 127An optional non-zero value to specify B<--comment> as private. 128 129=item --worktime 130 131An optional double precision number specifying the work time for B<--comment>. 132 133=item --extension_info 134 135If specified on the command line, the script returns the information about the 136extensions that are installed. 137 138=item --debug 139 140Enable tracing at the debug level of XMLRPC requests and responses. 141 142=back 143 144=head1 DESCRIPTION 145 146=cut 147 148pod2usage({'-verbose' => 1, '-exitval' => 0}) if $help; 149_syntaxhelp('URI unspecified') unless $Bugzilla_uri; 150 151# We will use this variable for SOAP call results. 152my $soapresult; 153 154# We will use this variable for function call results. 155my $result; 156 157=head2 Initialization 158 159Using the XMLRPC::Lite class, you set up a proxy, as shown in this script. 160Bugzilla's XMLRPC URI ends in C<xmlrpc.cgi>, so your URI looks along the lines 161of C<http://your.bugzilla.installation/path/to/bugzilla/xmlrpc.cgi>. 162 163=cut 164 165my $proxy = XMLRPC::Lite->proxy($Bugzilla_uri); 166 167=head2 Debugging 168 169Enable tracing at the debug level of XMLRPC requests and responses if requested. 170 171=cut 172 173if ($debug) { 174 $proxy->import(+trace => 'debug'); 175} 176 177=head2 Checking Bugzilla's version 178 179To make sure the Bugzilla you're connecting to supports the methods you wish to 180call, you may want to compare the result of C<Bugzilla.version> to the 181minimum required version your application needs. 182 183=cut 184 185$soapresult = $proxy->call('Bugzilla.version'); 186_die_on_fault($soapresult); 187print 'Connecting to a Bugzilla of version ' . $soapresult->result()->{version} . ".\n"; 188 189=head2 Checking Bugzilla's timezone 190 191To make sure that you understand the dates and times that Bugzilla returns to you, you may want to call C<Bugzilla.timezone>. 192 193=cut 194 195$soapresult = $proxy->call('Bugzilla.timezone'); 196_die_on_fault($soapresult); 197print 'Bugzilla\'s timezone is ' . $soapresult->result()->{timezone} . ".\n"; 198 199=head2 Logging In and Out 200 201=head3 Using Bugzilla's Environment Authentication 202 203Use a 204C<http://login:password@your.bugzilla.installation/path/to/bugzilla/xmlrpc.cgi> 205style URI. 206You don't log out if you're using this kind of authentication. 207 208=head3 Using Bugzilla's CGI Variable Authentication 209 210Use the C<User.login> and C<User.logout> calls to log in and out, as shown 211in this script. 212 213The C<Bugzilla_restrictlogin> parameter is optional. 214If omitted, Bugzilla's defaults apply (as specified by its C<restrictlogin> 215parameter). 216 217=cut 218 219if (defined($Bugzilla_login)) { 220 if ($Bugzilla_login ne '') { 221 # Log in. 222 $soapresult = $proxy->call('User.login', 223 { login => $Bugzilla_login, 224 password => $Bugzilla_password, 225 restrict_login => $Bugzilla_restrict } ); 226 $Bugzilla_token = $soapresult->result->{token}; 227 _die_on_fault($soapresult); 228 print "Login successful.\n"; 229 } 230 else { 231 # Log out. 232 $soapresult = $proxy->call('User.logout'); 233 _die_on_fault($soapresult); 234 print "Logout successful.\n"; 235 } 236} 237 238=head2 Getting Extension Information 239 240Returns all the information any extensions have decided to provide to the webservice. 241 242=cut 243 244if ($fetch_extension_info) { 245 $soapresult = $proxy->call('Bugzilla.extensions', {token => $Bugzilla_token}); 246 _die_on_fault($soapresult); 247 my $extensions = $soapresult->result()->{extensions}; 248 foreach my $extensionname (keys(%$extensions)) { 249 print "Extension '$extensionname' information\n"; 250 my $extension = $extensions->{$extensionname}; 251 foreach my $data (keys(%$extension)) { 252 print ' ' . $data . ' => ' . $extension->{$data} . "\n"; 253 } 254 } 255} 256 257=head2 Retrieving Bug Information 258 259Call C<Bug.get> with the ID of the bug you want to know more of. 260The call will return a C<Bugzilla::Bug> object. 261 262=cut 263 264if ($bug_id) { 265 $soapresult = $proxy->call('Bug.get', { ids => [$bug_id], token => $Bugzilla_token}); 266 _die_on_fault($soapresult); 267 $result = $soapresult->result; 268 my $bug = $result->{bugs}->[0]; 269 foreach my $field (keys(%$bug)) { 270 my $value = $bug->{$field}; 271 if (ref($value) eq 'HASH') { 272 foreach (keys %$value) { 273 print "$_: " . $value->{$_} . "\n"; 274 } 275 } 276 else { 277 print "$field: $value\n"; 278 } 279 } 280} 281 282=head2 Retrieving Product Information 283 284Call C<Product.get> with the name of the product you want to know more of. 285The call will return a C<Bugzilla::Product> object. 286 287=cut 288 289if ($product_name) { 290 $soapresult = $proxy->call('Product.get', {'names' => [$product_name], token => $Bugzilla_token}); 291 _die_on_fault($soapresult); 292 $result = $soapresult->result()->{'products'}->[0]; 293 294 # Iterate all entries, the values may be scalars or array refs with hash refs. 295 foreach my $key (sort(keys %$result)) { 296 my $value = $result->{$key}; 297 298 if (ref($value)) { 299 my $counter = 0; 300 foreach my $hash (@$value) { 301 while (my ($innerKey, $innerValue) = each %$hash) { 302 print "$key.$counter.$innerKey: $innerValue\n"; 303 } 304 ++$counter; 305 } 306 } 307 else { 308 print "$key: $value\n" 309 } 310 } 311} 312 313=head2 Creating A Bug 314 315Call C<Bug.create> with the settings read from the file indicated on 316the command line. The file must contain a valid anonymous hash to use 317as argument for the call to C<Bug.create>. 318The call will return a hash with a bug id for the newly created bug. 319 320=cut 321 322if ($create_file_name) { 323 my $bug_fields = do "$create_file_name"; 324 $bug_fields->{Bugzilla_token} = $Bugzilla_token; 325 $soapresult = $proxy->call('Bug.create', \%$bug_fields); 326 _die_on_fault($soapresult); 327 $result = $soapresult->result; 328 329 if (ref($result) eq 'HASH') { 330 foreach (keys(%$result)) { 331 print "$_: $$result{$_}\n"; 332 } 333 } 334 else { 335 print "$result\n"; 336 } 337 338} 339 340=head2 Getting Legal Field Values 341 342Call C<Bug.legal_values> with the name of the field (including custom 343select fields). The call will return a reference to an array with the 344list of legal values for this field. 345 346=cut 347 348if ($legal_field_values) { 349 $soapresult = $proxy->call('Bug.legal_values', {field => $legal_field_values, token => $Bugzilla_token} ); 350 _die_on_fault($soapresult); 351 $result = $soapresult->result; 352 353 print join("\n", @{$result->{values}}) . "\n"; 354} 355 356=head2 Adding a comment to a bug 357 358Call C<Bug.add_comment> with the bug id, the comment text, and optionally the number 359of hours you worked on the bug, and a boolean indicating if the comment is private 360or not. 361 362=cut 363 364if ($add_comment) { 365 if ($bug_id) { 366 $soapresult = $proxy->call('Bug.add_comment', {id => $bug_id, 367 comment => $add_comment, private => $private, work_time => $work_time, token => $Bugzilla_token}); 368 _die_on_fault($soapresult); 369 print "Comment added.\n"; 370 } 371 else { 372 print "A --bug_id must be supplied to add a comment."; 373 } 374} 375 376=head1 NOTES 377 378=head2 Character Set Encoding 379 380Make sure that your application either uses the same character set 381encoding as Bugzilla does, or that it converts correspondingly when using the 382web service API. 383By default, Bugzilla uses UTF-8 as its character set encoding. 384 385=head2 Format For Create File 386 387The create format file is a piece of Perl code, that should look something like 388this: 389 390 { 391 product => "TestProduct", 392 component => "TestComponent", 393 summary => "TestBug - created from bz_webservice_demo.pl", 394 version => "unspecified", 395 description => "This is a description of the bug... hohoho", 396 op_sys => "All", 397 platform => "All", 398 priority => "P4", 399 severity => "normal" 400 }; 401 402=head1 SEE ALSO 403 404There are code comments in C<bz_webservice_demo.pl> which might be of further 405help to you. 406 407=cut 408 409sub _die_on_fault { 410 my $soapresult = shift; 411 412 if ($soapresult->fault) { 413 my ($package, $filename, $line) = caller; 414 die $soapresult->faultcode . ' ' . $soapresult->faultstring . 415 " in SOAP call near $filename line $line.\n"; 416 } 417} 418 419sub _syntaxhelp { 420 my $msg = shift; 421 422 print "Error: $msg\n"; 423 pod2usage({'-verbose' => 0, '-exitval' => 1}); 424} 425