Class: Bicho::Client

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/bicho/client.rb

Overview

Client to query bugzilla

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

#logger

Constructor Details

#initialize(site_url) ⇒ Client

Returns a new instance of Client.

Parameters:

  • site_url (String)

    Bugzilla installation site url

Raises:

  • (ArgumentError)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/bicho/client.rb', line 96

def initialize(site_url)
  @plugins = []
  load_plugins!
  instantiate_plugins!

  if site_url.nil?
    @plugins.each do |pl_instance|
      if pl_instance.respond_to?(:default_site_url_hook)
        default = pl_instance.default_site_url_hook(logger)
        site_url = default unless default.nil?
      end
    end
  end

  # If the default url is still null, we can't continue
  raise ArgumentError, 'missing bugzilla site' if site_url.nil?

  @plugins.each do |pl_instance|
    if pl_instance.respond_to?(:transform_site_url_hook)
      site_url = pl_instance.transform_site_url_hook(site_url, logger)
    end
  end

  # Don't modify the original url
  @site_url = site_url.is_a?(URI) ? site_url.clone : URI.parse(site_url)

  api_url = @site_url.clone
  api_url.path = '/xmlrpc.cgi'

  @plugins.each do |pl_instance|
    # Modify API url
    if pl_instance.respond_to?(:transform_api_url_hook)
      api_url = pl_instance.transform_api_url_hook(api_url, logger)
    end
  end

  @api_url = api_url.is_a?(URI) ? api_url.clone : URI.parse(api_url)

  @client = XMLRPC::Client.new_from_uri(@api_url.to_s, nil, 900)
  @client.set_debug
  @plugins.each do |pl_instance|
    # Modify API url
    if pl_instance.respond_to?(:transform_xmlrpc_client_hook)
      pl_instance.transform_xmlrpc_client_hook(@client, logger)
    end
  end

  # User.login sets the credentials cookie for subsequent calls
  if @client.user && @client.password
    ret = @client.call('User.login', 'login' => @client.user, 'password' => @client.password, 'remember' => 0)
    handle_faults(ret)
    @userid = ret['id']
  end
end

Instance Attribute Details

#api_urlURI (readonly)

This URL is automatically inferred from the Client#site_url

Plugins can modify the inferred value by providing a transform_api_url_hook(url, logger) method returning the modified value.

Returns:

  • (URI)

    XML-RPC API end-point



77
78
79
# File 'lib/bicho/client.rb', line 77

def api_url
  @api_url
end

#site_urlURI (readonly)

This value is provided at construction time

Returns:

  • (URI)

    Bugzilla installation website



82
83
84
# File 'lib/bicho/client.rb', line 82

def site_url
  @site_url
end

#useridString (readonly)

Returns user id, available after login.

Returns:

  • (String)

    user id, available after login



85
86
87
# File 'lib/bicho/client.rb', line 85

def userid
  @userid
end

Instance Method Details

#add_attachment(summary, file, *ids, **kwargs) ⇒ Array<ID>

Add an attachment to bugs with given ids

Params:

Parameters:

  • summary
    • a short string describing the attachment

  • file
    • File

      object to attach

  • *ids
    • a list of bug ids to which the attachment will be added

  • **kwargs
    • optional keyword-args that may contain:

    • content_type - content type of the attachment (if ommited,

    ‘application/octet-stream’ will be used)

    • file_name - name of the file (if ommited, the base name of the

    provided file will be used)

    • patch? - flag saying that the attachment is a patch

    • private? - flag saying that the attachment is private

    • comment

Returns:

  • (Array<ID>)

    a list of the attachment id(s) created.



382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/bicho/client.rb', line 382

def add_attachment(summary, file, *ids, **kwargs)
  params = {}
  params[:ids] = ids
  params[:summary] = summary
  params[:content_type] = kwargs.fetch(:content_type, 'application/octet-stream')
  params[:file_name] = kwargs.fetch(:file_name, File.basename(file))
  params[:is_patch] = kwargs[:patch?] if kwargs[:patch?]
  params[:is_private] = kwargs[:private?] if kwargs[:private?]
  params[:comment] = kwargs[:comment] if kwargs[:comment]
  params[:data] = XMLRPC::Base64.new(file.read)
  ret = @client.call('Bug.add_attachment', params)
  handle_faults(ret)
  ret['ids']
end


172
173
174
# File 'lib/bicho/client.rb', line 172

def cookie
  @client.cookie
end

#create_bug(product, component, summary, version, **kwargs) ⇒ Object

Create a bug

Return the new bug ID

Parameters:

  • product
    • the name of the product the bug is being filed against

  • component
    • the name of a component in the product above.

  • summary
    • a brief description of the bug being filed.

  • version
    • version of the product above; the version the bug was found in.

  • **kwargs
    • keyword-args containing optional/defaulted params



200
201
202
203
204
205
206
207
208
209
210
# File 'lib/bicho/client.rb', line 200

def create_bug(product, component, summary, version, **kwargs)
  params = {}
  params = params.merge(kwargs)
  params[:product] = product
  params[:component] = component
  params[:summary] = summary
  params[:version] = version
  ret = @client.call('Bug.create', params)
  handle_faults(ret)
  ret['id']
end

#expand_named_query(what) ⇒ Object

Given a named query’s name, runs it on the server



251
252
253
254
255
256
257
# File 'lib/bicho/client.rb', line 251

def expand_named_query(what)
  url = @api_url.clone
  url.path = '/buglist.cgi'
  url.query = "cmdtype=runnamed&namedcmd=#{URI.escape(what)}&ctype=atom"
  logger.info("Expanding named query: '#{what}' to #{url.request_uri}")
  fetch_named_query_url(url, 5)
end

#fetch_named_query_url(url, redirects_left) ⇒ Object

Fetches a named query by its full url



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/bicho/client.rb', line 277

def fetch_named_query_url(url, redirects_left)
  raise 'You need to be authenticated to use named queries' unless @userid
  http = Net::HTTP.new(@api_url.host, @api_url.port)
  http.set_debug_output(Bicho::LoggerIODevice.new)
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  http.use_ssl = (@api_url.scheme == 'https')
  # request = Net::HTTP::Get.new(url.request_uri, {'Cookie' => self.cookie})
  request = Net::HTTP::Get.new(url.request_uri)
  request.basic_auth @api_url.user, @api_url.password
  response = http.request(request)
  case response
  when Net::HTTPSuccess
    bugs = []
    begin
      xml = Nokogiri::XML.parse(response.body)
      xml.root.xpath('//xmlns:entry/xmlns:link/@href', xml.root.namespace).each do |attr|
        uri = URI.parse attr.value
        bugs << uri.query.split('=')[1]
      end
      return bugs
    rescue Nokogiri::XML::XPath::SyntaxError
      raise "Named query '#{url.request_uri}' not found"
    end
  when Net::HTTPRedirection
    location = response['location']
    if redirects_left.zero?
      raise "Maximum redirects exceeded (redirected to #{location})"
    end
    new_location_uri = URI.parse(location)
    logger.debug("Moved to #{new_location_uri}")
    fetch_named_query_url(new_location_uri, redirects_left - 1)
  else
    raise "Error when expanding named query '#{url.request_uri}': #{response}"
  end
end

#get_attachments(*ids) ⇒ Array<Attachment>

given bugs.

Payload is lazy-loaded

Returns:

  • (Array<Attachment>)

    a list of attachments for the



352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/bicho/client.rb', line 352

def get_attachments(*ids)
  params = {}
  params[:ids] = normalize_ids ids

  ret = @client.call('Bug.attachments',
                     params.merge(exclude_fields: ['data']))
  handle_faults(ret)
  ret['bugs'].map do |_, attachments_data|
    attachments_data.map do |attachment_data|
      Attachment.new(self, @client, attachment_data)
    end
  end.flatten
end

#get_bug(id) ⇒ Bug

Gets a single bug

Returns:

  • (Bug)

    a single bug by id



315
316
317
# File 'lib/bicho/client.rb', line 315

def get_bug(id)
  get_bugs(id).first
end

#get_bugs(*ids) ⇒ Array<Bug>

Retrieves one or more bugs by id

Returns:

  • (Array<Bug>)

    a list of bugs



321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/bicho/client.rb', line 321

def get_bugs(*ids)
  params = {}
  params[:ids] = normalize_ids ids

  bugs = []
  ret = @client.call('Bug.get', params)
  handle_faults(ret)
  ret['bugs'].each do |bug_data|
    bugs << Bug.new(self, bug_data)
  end
  bugs
end

#get_history(*ids) ⇒ Array<History>

Returns the history of the given bugs.

Returns:

  • (Array<History>)

    the history of the given bugs



335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/bicho/client.rb', line 335

def get_history(*ids)
  params = {}
  params[:ids] = normalize_ids ids

  histories = []
  ret = @client.call('Bug.history', params)
  handle_faults(ret)
  ret['bugs'].each do |history_data|
    histories << History.new(self, history_data)
  end
  histories
end

#handle_faults(ret) ⇒ Object



176
177
178
179
180
181
182
# File 'lib/bicho/client.rb', line 176

def handle_faults(ret)
  if ret.key?('faults')
    ret['faults'].each do |fault|
      logger.error fault
    end
  end
end

#instantiate_plugins!Object

instantiate all plugin classes in the Bicho::Plugins module and add them to the list of known plugins



163
164
165
166
167
168
169
170
# File 'lib/bicho/client.rb', line 163

def instantiate_plugins!
  ::Bicho::Plugins.constants.each do |cnt|
    pl_class = ::Bicho::Plugins.const_get(cnt)
    pl_instance = pl_class.new
    logger.debug("Loaded: #{pl_instance}")
    @plugins << pl_instance
  end
end

#load_plugins!Object

ruby-load all the files in the plugins directory



152
153
154
155
156
157
158
159
# File 'lib/bicho/client.rb', line 152

def load_plugins!
  # Scan plugins
  plugin_glob = File.join(File.dirname(__FILE__), 'plugins', '*.rb')
  Dir.glob(plugin_glob).each do |plugin|
    logger.debug("Loading file: #{plugin}")
    require plugin
  end
end

#normalize_ids(ids) ⇒ Object

normalize bug ids

Parameters:

  • ids
    • array of bug numbers (Integer) or bug aliases (String)



264
265
266
267
268
269
270
271
272
# File 'lib/bicho/client.rb', line 264

def normalize_ids(ids)
  ids.collect(&:to_s).map do |what|
    if what =~ /^[0-9]+$/
      next what.to_i
    else
      next expand_named_query(what)
    end
  end.flatten
end

#search_bugs(query) ⇒ Object

Search for a bug

query has to be either a Query object or a String that will be searched in the summary of the bugs.



235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/bicho/client.rb', line 235

def search_bugs(query)
  # allow plain strings to be passed, interpretting them
  query = Query.new.summary(query) if query.is_a?(String)

  ret = @client.call('Bug.search', query.query_map)
  handle_faults(ret)
  bugs = []
  ret['bugs'].each do |bug_data|
    bugs << Bug.new(self, bug_data)
  end
  bugs
end

#update_bug(id, **kwargs) ⇒ Object

Update a bug

Parameters:

  • id
    • bug number (Integer) or alias (String) of bug to be updated

  • **kwargs
    • keyword-args containing optional/defaulted params



219
220
221
222
223
224
225
226
227
# File 'lib/bicho/client.rb', line 219

def update_bug(id, **kwargs)
  params = {}
  params = params.merge(kwargs)
  params[:ids] = normalize_ids [id]
  ret = @client.call('Bug.update', params)
  logger.info "Bug.update returned #{ret.inspect}"
  handle_faults(ret)
  ret.dig('bugs', 0, 'id')
end

#versionObject

Return Bugzilla API version



185
186
187
188
189
# File 'lib/bicho/client.rb', line 185

def version
  ret = @client.call('Bugzilla.version')
  handle_faults(ret)
  ret['version']
end