jenkins_api_client 0.14.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +5 -23
  4. data/CHANGELOG.md +104 -35
  5. data/CONTRIBUTORS.md +1 -11
  6. data/Gemfile +4 -2
  7. data/README.md +107 -7
  8. data/Rakefile +1 -0
  9. data/Vagrantfile +6 -8
  10. data/jenkins_api_client.gemspec +30 -12
  11. data/lib/jenkins_api_client/cli/helper.rb +1 -0
  12. data/lib/jenkins_api_client/client.rb +162 -69
  13. data/lib/jenkins_api_client/exceptions.rb +26 -17
  14. data/lib/jenkins_api_client/job.rb +321 -75
  15. data/lib/jenkins_api_client/node.rb +22 -10
  16. data/lib/jenkins_api_client/plugin_manager.rb +460 -0
  17. data/lib/jenkins_api_client/urihelper.rb +17 -0
  18. data/lib/jenkins_api_client/user.rb +4 -2
  19. data/lib/jenkins_api_client/version.rb +3 -3
  20. data/lib/jenkins_api_client/view.rb +10 -7
  21. data/lib/jenkins_api_client.rb +1 -0
  22. data/scripts/login_with_pry.rb +54 -0
  23. data/spec/func_tests/client_spec.rb +3 -3
  24. data/spec/func_tests/job_spec.rb +90 -7
  25. data/spec/func_tests/{node_spec.rb → node_spec.rb.pending} +9 -9
  26. data/spec/func_tests/plugin_spec.rb +148 -0
  27. data/spec/unit_tests/client_spec.rb +108 -27
  28. data/spec/unit_tests/fake_http_response.rb +9 -0
  29. data/spec/unit_tests/fixtures/files/available_plugins.json +1 -0
  30. data/spec/unit_tests/fixtures/files/installed_plugins.json +1 -0
  31. data/spec/unit_tests/fixtures/files/updatable_plugins.json +1 -0
  32. data/spec/unit_tests/job_spec.rb +109 -6
  33. data/spec/unit_tests/node_spec.rb +18 -6
  34. data/spec/unit_tests/plugin_spec.rb +165 -0
  35. data/spec/unit_tests/spec_helper.rb +11 -1
  36. data/spec/unit_tests/system_spec.rb +2 -1
  37. data/spec/unit_tests/user_spec.rb +1 -1
  38. data/travis/hudson.model.UpdateCenter.xml +7 -0
  39. data/travis/setup.sh +2 -1
  40. metadata +76 -64
@@ -35,7 +35,7 @@ require 'logger'
35
35
  #
36
36
  module JenkinsApi
37
37
  # This is the client class that acts as the bridge between the subclasses and
38
- # Jnekins. This class contains methods that performs GET and POST requests
38
+ # Jenkins. This class contains methods that performs GET and POST requests
39
39
  # for various operations.
40
40
  #
41
41
  class Client
@@ -44,6 +44,8 @@ module JenkinsApi
44
44
  DEFAULT_SERVER_PORT = 8080
45
45
  # Default timeout in seconds to be used while performing operations
46
46
  DEFAULT_TIMEOUT = 120
47
+ DEFAULT_HTTP_OPEN_TIMEOUT = 10
48
+ DEFAULT_HTTP_READ_TIMEOUT = 120
47
49
  # Parameters that are permitted as options while initializing the client
48
50
  VALID_PARAMS = [
49
51
  "server_url",
@@ -58,53 +60,39 @@ module JenkinsApi
58
60
  "log_location",
59
61
  "log_level",
60
62
  "timeout",
63
+ "http_open_timeout",
64
+ "http_read_timeout",
61
65
  "ssl",
62
66
  "follow_redirects",
63
- "identity_file"
67
+ "identity_file",
68
+ "cookies"
64
69
  ].freeze
65
70
 
66
71
  # Initialize a Client object with Jenkins CI server credentials
67
72
  #
68
73
  # @param args [Hash] Arguments to connect to Jenkins server
69
74
  #
70
- # @option args [String] :server_ip
71
- # the IP address of the Jenkins CI server
72
- # @option args [String] :server_port
73
- # the port on which the Jenkins listens
74
- # @option args [String] :server_url
75
- # the full URL address of the Jenkins CI server (http/https)
76
- # @option args [String] :username
77
- # the username used for connecting to the server (optional)
78
- # @option args [String] :password
79
- # the password for connecting to the CI server (optional)
80
- # @option args [String] :password_base64
81
- # the password with base64 encoded format for connecting to the CI
75
+ # @option args [String] :server_ip the IP address of the Jenkins CI server
76
+ # @option args [String] :server_port the port on which the Jenkins listens
77
+ # @option args [String] :server_url the full URL address of the Jenkins CI server (http/https). This can include username/password. :username/:password options will override any user/pass value in the URL
78
+ # @option args [String] :username the username used for connecting to the server (optional)
79
+ # @option args [String] :password the password or API Key for connecting to the CI server (optional)
80
+ # @option args [String] :password_base64 the password with base64 encoded format for connecting to the CI
82
81
  # server (optional)
83
- # @option args [String] :identity_file
84
- # the priviate key file for Jenkins CLI authentication,
85
- # it is used only for executing CLI commands.
86
- # also remember to upload the public key to
87
- # http://<Server IP>:<Server Port>/user/<Username>/configure
88
- # @option args [String] :proxy_ip
89
- # the proxy IP address
90
- # @option args [String] :proxy_port
91
- # the proxy port
92
- # @option args [String] :jenkins_path ("/")
93
- # the optional context path for Jenkins
94
- # @option args [Boolean] :ssl (false)
95
- # indicates if Jenkins is accessible over HTTPS
96
- # @option args [Boolean] :follow_redirects
97
- # This argument causes the client to follow a redirect (jenkins can
82
+ # @option args [String] :identity_file the priviate key file for Jenkins CLI authentication,
83
+ # it is used only for executing CLI commands. Also remember to upload the public key to
84
+ # <Server IP>:<Server Port>/user/<Username>/configure
85
+ # @option args [String] :proxy_ip the proxy IP address
86
+ # @option args [String] :proxy_port the proxy port
87
+ # @option args [String] :jenkins_path ("/") the optional context path for Jenkins
88
+ # @option args [Boolean] :ssl (false) indicates if Jenkins is accessible over HTTPS
89
+ # @option args [Boolean] :follow_redirects this argument causes the client to follow a redirect (jenkins can
98
90
  # return a 30x when starting a build)
99
- # @option args [Fixnum] :timeout (120)
100
- # This argument sets the timeout for operations that take longer (in
101
- # seconds)
102
- # @option args [String] :log_location (STDOUT)
103
- # The location for the log file
104
- # @option args [Fixnum] :log_level (Logger::INFO)
105
- # The level for messages to be logged. Should be one of:
106
- # Logger::DEBUG (0), Logger::INFO (1), Logger::WARN (2), Logger::ERROR
107
- # (2), Logger::FATAL (3) (Defaults to Logger::INFO)
91
+ # @option args [Fixnum] :timeout (120) This argument sets the timeout for operations that take longer (in seconds)
92
+ # @option args [String] :log_location (STDOUT) the location for the log file
93
+ # @option args [Fixnum] :log_level (Logger::INFO) The level for messages to be logged. Should be one of:
94
+ # Logger::DEBUG (0), Logger::INFO (1), Logger::WARN (2), Logger::ERROR (2), Logger::FATAL (3)
95
+ # @option args [String] :cookies Cookies to be sent with all requests in the format: name=value; name2=value2
108
96
  #
109
97
  # @return [JenkinsApi::Client] a client object to Jenkins API
110
98
  #
@@ -117,12 +105,26 @@ module JenkinsApi
117
105
  end
118
106
  end if args.is_a? Hash
119
107
 
120
- # Server IP or Server URL must be specifiec
108
+ # Server IP or Server URL must be specific
121
109
  unless @server_ip || @server_url
122
110
  raise ArgumentError, "Server IP or Server URL is required to connect" +
123
111
  " to Jenkins"
124
112
  end
125
113
 
114
+ # Get info from the server_url, if we got one
115
+ if @server_url
116
+ server_uri = URI.parse(@server_url)
117
+ @server_ip = server_uri.host
118
+ @server_port = server_uri.port
119
+ @ssl = server_uri.scheme == "https"
120
+ @jenkins_path = server_uri.path
121
+
122
+ # read username and password from the URL
123
+ # only set if @username and @password are not already set via explicit options
124
+ @username ||= server_uri.user
125
+ @password ||= server_uri.password
126
+ end
127
+
126
128
  # Username/password are optional as some jenkins servers do not require
127
129
  # authentication
128
130
  if @username && !(@password || @password_base64)
@@ -133,19 +135,12 @@ module JenkinsApi
133
135
  " both left nil"
134
136
  end
135
137
 
136
- # Get info from the server_url, if we got one
137
- if @server_url
138
- server_uri = URI.parse(@server_url)
139
- @server_ip = server_uri.host
140
- @server_port = server_uri.port
141
- @ssl = server_uri.scheme == "https"
142
- @jenkins_path = server_uri.path
143
- end
144
-
145
138
  @jenkins_path ||= ""
146
139
  @jenkins_path.gsub!(/\/$/,"") # remove trailing slash if there is one
147
140
  @server_port = DEFAULT_SERVER_PORT unless @server_port
148
141
  @timeout = DEFAULT_TIMEOUT unless @timeout
142
+ @http_open_timeout = DEFAULT_HTTP_OPEN_TIMEOUT unless @http_open_timeout
143
+ @http_read_timeout = DEFAULT_HTTP_READ_TIMEOUT unless @http_read_timeout
149
144
  @ssl ||= false
150
145
 
151
146
  # Setting log options
@@ -212,6 +207,15 @@ module JenkinsApi
212
207
  JenkinsApi::Client::BuildQueue.new(self)
213
208
  end
214
209
 
210
+ # Creates an instance to the PluginManager by passing a reference to self
211
+ #
212
+ # @return [JenkinsApi::Client::PluginManager] an object to PluginManager
213
+ # subclass
214
+ #
215
+ def plugin
216
+ JenkinsApi::Client::PluginManager.new(self)
217
+ end
218
+
215
219
  # Creates an instance of the User class by passing a reference to self
216
220
  #
217
221
  # @return [JenkinsApi::Client::User] An object of User subclass
@@ -240,7 +244,9 @@ module JenkinsApi
240
244
  " @crumbs_enabled=#{@crumbs_enabled.inspect}," +
241
245
  " @follow_redirects=#{@follow_redirects.inspect}," +
242
246
  " @jenkins_path=#{@jenkins_path.inspect}," +
243
- " @timeout=#{@timeout.inspect}>"
247
+ " @timeout=#{@timeout.inspect}>," +
248
+ " @http_open_timeout=#{@http_open_timeout.inspect}>," +
249
+ " @http_read_timeout=#{@http_read_timeout.inspect}>"
244
250
  end
245
251
 
246
252
  # Connects to the Jenkins server, sends the specified request and returns
@@ -253,6 +259,7 @@ module JenkinsApi
253
259
  #
254
260
  def make_http_request(request, follow_redirect = @follow_redirects)
255
261
  request.basic_auth @username, @password if @username
262
+ request['Cookie'] = @cookies if @cookies
256
263
 
257
264
  if @proxy_ip
258
265
  http = Net::HTTP::Proxy(@proxy_ip, @proxy_port).new(@server_ip, @server_port)
@@ -265,6 +272,9 @@ module JenkinsApi
265
272
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
266
273
  end
267
274
 
275
+ http.open_timeout = @http_open_timeout
276
+ http.read_timeout = @http_read_timeout
277
+
268
278
  response = http.request(request)
269
279
  case response
270
280
  when Net::HTTPRedirection then
@@ -278,6 +288,11 @@ module JenkinsApi
278
288
  )
279
289
  end
280
290
  end
291
+
292
+ # Pick out some useful header info before we return
293
+ @jenkins_version = response['X-Jenkins']
294
+ @hudson_version = response['X-Hudson']
295
+
281
296
  return response
282
297
  end
283
298
  protected :make_http_request
@@ -288,8 +303,8 @@ module JenkinsApi
288
303
  # @return [Net::HTTP::Response] Response from Jenkins for "/"
289
304
  #
290
305
  def get_root
291
- @logger.info "GET /"
292
- request = Net::HTTP::Get.new("/")
306
+ @logger.info "GET #{@jenkins_path}/"
307
+ request = Net::HTTP::Get.new("#{@jenkins_path}/")
293
308
  make_http_request(request)
294
309
  end
295
310
 
@@ -301,7 +316,7 @@ module JenkinsApi
301
316
  # @param [Boolean] raw_response Return complete Response object instead of
302
317
  # JSON body of response
303
318
  #
304
- # @return [String, JSON] JSON response from Jenkins
319
+ # @return [String, Hash] JSON response from Jenkins
305
320
  #
306
321
  def api_get_request(url_prefix, tree = nil, url_suffix ="/api/json",
307
322
  raw_response = false)
@@ -312,7 +327,6 @@ module JenkinsApi
312
327
  else
313
328
  to_get = "#{url_prefix}#{url_suffix}"
314
329
  end
315
- to_get = URI.escape(to_get)
316
330
  request = Net::HTTP::Get.new(to_get)
317
331
  @logger.info "GET #{to_get}"
318
332
  response = make_http_request(request)
@@ -337,10 +351,8 @@ module JenkinsApi
337
351
 
338
352
  # Added form_data default {} instead of nil to help with proxies
339
353
  # that barf with empty post
340
- url_prefix = URI.escape("#{@jenkins_path}#{url_prefix}")
341
- request = Net::HTTP::Post.new("#{url_prefix}")
354
+ request = Net::HTTP::Post.new("#{@jenkins_path}#{url_prefix}")
342
355
  @logger.info "POST #{url_prefix}"
343
- request.content_type = 'application/json'
344
356
  if @crumbs_enabled
345
357
  request[@crumb["crumbRequestField"]] = @crumb["crumb"]
346
358
  end
@@ -377,8 +389,7 @@ module JenkinsApi
377
389
  # @return [String] XML configuration obtained from Jenkins
378
390
  #
379
391
  def get_config(url_prefix)
380
- url_prefix = URI.escape("#{@jenkins_path}#{url_prefix}")
381
- request = Net::HTTP::Get.new("#{url_prefix}/config.xml")
392
+ request = Net::HTTP::Get.new("#{@jenkins_path}#{url_prefix}/config.xml")
382
393
  @logger.info "GET #{url_prefix}/config.xml"
383
394
  response = make_http_request(request)
384
395
  handle_exception(response, "body")
@@ -392,15 +403,22 @@ module JenkinsApi
392
403
  # @return [String] Response code returned from Jenkins
393
404
  #
394
405
  def post_config(url_prefix, xml)
406
+ post_data(url_prefix, xml, 'application/xml')
407
+ end
408
+
409
+ def post_json(url_prefix, json)
410
+ post_data(url_prefix, json, 'application/json')
411
+ end
412
+
413
+ def post_data(url_prefix, data, content_type)
395
414
  retries = @crumb_max_retries
396
415
  begin
397
416
  refresh_crumbs
398
417
 
399
- url_prefix = URI.escape("#{@jenkins_path}#{url_prefix}")
400
- request = Net::HTTP::Post.new("#{url_prefix}")
418
+ request = Net::HTTP::Post.new("#{@jenkins_path}#{url_prefix}")
401
419
  @logger.info "POST #{url_prefix}"
402
- request.body = xml
403
- request.content_type = 'application/xml'
420
+ request.body = data
421
+ request.content_type = content_type
404
422
  if @crumbs_enabled
405
423
  request[@crumb["crumbRequestField"]] = @crumb["crumb"]
406
424
  end
@@ -425,6 +443,24 @@ module JenkinsApi
425
443
  end
426
444
  end
427
445
 
446
+ def init_update_center
447
+ @logger.info "Initializing Jenkins Update Center..."
448
+ @logger.debug "Obtaining the JSON data for Update Center..."
449
+ # TODO: Clean me up
450
+ update_center_data = open("http://updates.jenkins-ci.org/update-center.json").read
451
+ # The Jenkins mirror returns the data in the following format
452
+ # updateCenter.post(
453
+ # {.. JSON data...}
454
+ # );
455
+ # which is used by the Javascript used by the Jenkins UI to send to Jenkins.
456
+ #
457
+ update_center_data.gsub!("updateCenter.post(\n", "")
458
+ update_center_data.gsub!("\n);", "")
459
+
460
+ @logger.debug "Posting the obtained JSON to Jenkins Update Center..."
461
+ post_json("/updateCenter/byId/default/postBack", update_center_data)
462
+ end
463
+
428
464
  # Checks if Jenkins uses crumbs (i.e) the XSS disable option is checked in
429
465
  # Jenkins' security settings
430
466
  #
@@ -445,21 +481,64 @@ module JenkinsApi
445
481
  end
446
482
 
447
483
  # Obtains the jenkins version from the API
484
+ # Only queries Jenkins if the version is not already stored.
485
+ # Note that the version is auto-updated after every request made to Jenkins
486
+ # since it is returned as a header in every response
448
487
  #
449
- # @return Jenkins version
488
+ # @return [String] Jenkins version
450
489
  #
451
490
  def get_jenkins_version
452
- response = get_root
453
- response["X-Jenkins"]
491
+ get_root if @jenkins_version.nil?
492
+ @jenkins_version
454
493
  end
455
494
 
456
495
  # Obtain the Hudson version of the CI server
496
+ # Only queries Hudson/Jenkins if the version is not already stored.
497
+ # Note that the version is auto-updated after every request made to Jenkins
498
+ # since it is returned as a header in every response
457
499
  #
458
500
  # @return [String] Version of Hudson on Jenkins server
459
501
  #
460
502
  def get_hudson_version
461
- response = get_root
462
- response["X-Hudson"]
503
+ get_root if @hudson_version.nil?
504
+ @hudson_version
505
+ end
506
+
507
+ # Converts a version string to a list of integers
508
+ # This makes it easier to compare versions since in 'version-speak',
509
+ # v 1.2 is a lot older than v 1.102 - and simple < > on version
510
+ # strings doesn't work so well
511
+ def deconstruct_version_string(version)
512
+ match = version.match(/^(\d+)\.(\d+)(?:\.(\d+))?$/)
513
+
514
+ # Match should have 4 parts [0] = input string, [1] = major
515
+ # [2] = minor, [3] = patch (possibly blank)
516
+ if match && match.size == 4
517
+ return [match[1].to_i, match[2].to_i, match[3].to_i || 0]
518
+ else
519
+ return nil
520
+ end
521
+ end
522
+
523
+ # Compare two version strings (A and B)
524
+ # if A == B, returns 0
525
+ # if A > B, returns 1
526
+ # if A < B, returns -1
527
+ def compare_versions(version_a, version_b)
528
+ if version_a == version_b
529
+ return 0
530
+ else
531
+ version_a_d = deconstruct_version_string(version_a)
532
+ version_b_d = deconstruct_version_string(version_b)
533
+
534
+ if version_a_d[0] > version_b_d[0] ||
535
+ (version_a_d[0] == version_b_d[0] && version_a_d[1] > version_b_d[1]) ||
536
+ (version_a_d[0] == version_b_d[0] && version_a_d[1] == version_b_d[1] && version_a_d[2] > version_b_d[2])
537
+ return 1
538
+ else
539
+ return -1
540
+ end
541
+ end
463
542
  end
464
543
 
465
544
  # Obtain the date of the Jenkins server
@@ -471,6 +550,17 @@ module JenkinsApi
471
550
  response["Date"]
472
551
  end
473
552
 
553
+ # Executes the provided groovy script on the Jenkins CI server
554
+ #
555
+ # @param [String] script_text The text of the groovy script to execute
556
+ #
557
+ # @return [String] The output of the executed groovy script
558
+ #
559
+ def exec_script(script_text)
560
+ response = api_post_request('/scriptText', {'script' => script_text}, true)
561
+ response.body
562
+ end
563
+
474
564
  # Execute the Jenkins CLI
475
565
  #
476
566
  # @param command [String] command name
@@ -581,7 +671,7 @@ module JenkinsApi
581
671
  # @param [Boolean] send_json Boolean value used to determine whether to
582
672
  # load the JSON or send the response as is.
583
673
  #
584
- # @return [String, JSON] Response returned whether loaded JSON or raw
674
+ # @return [String, Hash] Response returned whether loaded JSON or raw
585
675
  # string
586
676
  #
587
677
  # @raise [Exceptions::Unauthorized] When invalid credentials are
@@ -633,7 +723,10 @@ module JenkinsApi
633
723
  when 404
634
724
  raise Exceptions::NotFound.new @logger
635
725
  when 500
636
- raise Exceptions::InternalServerError.new @logger
726
+ matched = response.body.match(/Exception: (.*)<br>/)
727
+ api_message = matched[1] unless matched.nil?
728
+ @logger.debug "API message: #{api_message}"
729
+ raise Exceptions::InternalServerError.new(@logger, api_message)
637
730
  when 503
638
731
  raise Exceptions::ServiceUnavailable.new @logger
639
732
  else
@@ -41,8 +41,8 @@ module JenkinsApi
41
41
  #
42
42
  class NothingSubmitted < ApiException
43
43
  def initialize(logger, message = "", log_level = Logger::ERROR)
44
- msg = "Nothing is submitted. #{message}"
45
- super(logger, msg)
44
+ message = "Nothing is submitted." if message.empty?
45
+ super(logger, message)
46
46
  end
47
47
  end
48
48
 
@@ -80,8 +80,8 @@ module JenkinsApi
80
80
  #
81
81
  class Unauthorized < ApiException
82
82
  def initialize(logger, message = "", log_level = Logger::ERROR)
83
- msg = "Invalid credentials are provided. #{message}"
84
- super(logger, msg, Logger::FATAL)
83
+ message = "Invalid credentials are provided." if message.empty?
84
+ super(logger, message, Logger::FATAL)
85
85
  end
86
86
  end
87
87
  # Support for backward compatibility
@@ -158,8 +158,8 @@ module JenkinsApi
158
158
  #
159
159
  class CrumbNotFound < NotFound
160
160
  def initialize(logger, message = "", log_level = Logger::ERROR)
161
- msg = "No crumb available on the server. #{message}"
162
- super(logger, msg)
161
+ message = "No crumb available on the server." if message.empty?
162
+ super(logger, message)
163
163
  end
164
164
  end
165
165
  # Support for backward compatibility
@@ -196,14 +196,14 @@ module JenkinsApi
196
196
  end
197
197
 
198
198
  # This exception class handles cases where the Jenkins API returns with a
199
- # 500 Internel Server Error.
199
+ # 500 Internal Server Error.
200
200
  #
201
201
  class InternalServerError < ApiException
202
202
  def initialize(logger, message = "", log_level = Logger::ERROR)
203
- msg = "Internel Server Error. Perhaps the in-memory configuration of" +
204
- " Jenkins is different from the disk configuration." +
205
- " Please try to reload the configuration #{message}"
206
- super(logger, msg)
203
+ message = "Internal Server Error. Perhaps the in-memory configuration" +
204
+ " Jenkins is different from the disk configuration. Please try to" +
205
+ " reload the configuration" if message.nil? || message.empty?
206
+ super(logger, message)
207
207
  end
208
208
  end
209
209
  # Support for backward compatibility
@@ -214,10 +214,10 @@ module JenkinsApi
214
214
  #
215
215
  class ServiceUnavailable < ApiException
216
216
  def initialize(logger, message = "", log_level = Logger::ERROR)
217
- msg = "Jenkins is being reloaded or restarted. Please wait till" +
218
- " Jenkins is completely back online. This can be" +
219
- " programatically achieved by System#wait_for_ready #{message}"
220
- super(logger, msg)
217
+ message = "Jenkins is being reloaded or restarted. Please wait till" +
218
+ " Jenkins is completely back online. This can be" +
219
+ " programatically achieved by System#wait_for_ready" if message.empty?
220
+ super(logger, message)
221
221
  end
222
222
  end
223
223
  # Support for backward compatibility
@@ -227,11 +227,20 @@ module JenkinsApi
227
227
  #
228
228
  class CLIError < ApiException
229
229
  def initialize(logger, message = "", log_level = Logger::ERROR)
230
- msg = "Execute CLI Error. #{message}"
231
- super(logger, msg)
230
+ message = "Unable to execute the command." if message.empty?
231
+ super(logger, message)
232
232
  end
233
233
  end
234
234
  # Support for backward compatibility
235
235
  CLIException = CLIError
236
+
237
+ # Exception when a particular plugin is not found
238
+ #
239
+ class PluginNotFound < NotFound
240
+ def initialize(logger, message = "", log_level = Logger::ERROR)
241
+ message = "The specified plugin is not found" if message.empty?
242
+ super(logger, message)
243
+ end
244
+ end
236
245
  end
237
246
  end