lhj-tools 0.2.13 → 0.2.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,837 +0,0 @@
1
- require 'json'
2
- require 'net/http'
3
- require 'net/https'
4
- require 'nokogiri'
5
- require 'base64'
6
- require 'uri'
7
- require 'logger'
8
- require 'open-uri'
9
-
10
- module Lhj
11
- module JenkinsApi
12
- # This is the client class that acts as the bridge between the subclasses and
13
- # Jenkins. This class contains methods that performs GET and POST requests
14
- # for various operations.
15
- #
16
- class Client
17
- attr_accessor :timeout, :logger
18
- # Default port to be used to connect to Jenkins
19
- DEFAULT_SERVER_PORT = 8080
20
- # Default timeout in seconds to be used while performing operations
21
- DEFAULT_TIMEOUT = 120
22
- DEFAULT_HTTP_OPEN_TIMEOUT = 10
23
- DEFAULT_HTTP_READ_TIMEOUT = 120
24
- # Parameters that are permitted as options while initializing the client
25
- VALID_PARAMS = [
26
- "server_url",
27
- "server_ip",
28
- "server_port",
29
- "proxy_ip",
30
- "proxy_port",
31
- "proxy_protocol",
32
- "jenkins_path",
33
- "username",
34
- "password",
35
- "password_base64",
36
- "logger",
37
- "log_location",
38
- "log_level",
39
- "timeout",
40
- "http_open_timeout",
41
- "http_read_timeout",
42
- "ssl",
43
- "pkcs_file_path",
44
- "pass_phrase",
45
- "ca_file",
46
- "follow_redirects",
47
- "identity_file",
48
- "cookies"
49
- ].freeze
50
-
51
- # Initialize a Client object with Jenkins CI server credentials
52
- #
53
- # @param args [Hash] Arguments to connect to Jenkins server
54
- #
55
- # @option args [String] :server_ip the IP address of the Jenkins CI server
56
- # @option args [String] :server_port the port on which the Jenkins listens
57
- # @option args [String] :server_url the full URL address of the Jenkins CI server (http/https). This can include
58
- # username/password. :username/:password options will override any user/pass value in the URL
59
- # @option args [String] :username the username used for connecting to the server (optional)
60
- # @option args [String] :password the password or API Key for connecting to the CI server (optional)
61
- # @option args [String] :password_base64 the password with base64 encoded format for connecting to the CI
62
- # server (optional)
63
- # @option args [String] :identity_file the priviate key file for Jenkins CLI authentication,
64
- # it is used only for executing CLI commands. Also remember to upload the public key to
65
- # <Server IP>:<Server Port>/user/<Username>/configure
66
- # @option args [String] :proxy_ip the proxy IP address
67
- # @option args [String] :proxy_port the proxy port
68
- # @option args [String] :proxy_protocol the proxy protocol ('socks' or 'http' (defaults to HTTP)
69
- # @option args [String] :jenkins_path ("/") the optional context path for Jenkins
70
- # @option args [Boolean] :ssl (false) indicates if Jenkins is accessible over HTTPS
71
- # @option args [String] :pkcs_file_path ("/") the optional context path for pfx or p12 binary certificate file
72
- # @option args [String] :pass_phrase password for pkcs_file_path certificate file
73
- # @option args [String] :ca_file the path to a PEM encoded file containing trusted certificates used to verify peer certificate
74
- # @option args [Boolean] :follow_redirects this argument causes the client to follow a redirect (jenkins can
75
- # return a 30x when starting a build)
76
- # @option args [Fixnum] :timeout (120) This argument sets the timeout for operations that take longer (in seconds)
77
- # @option args [Logger] :logger a Logger object, used to override the default logger (optional)
78
- # @option args [String] :log_location (STDOUT) the location for the log file
79
- # @option args [Fixnum] :log_level (Logger::INFO) The level for messages to be logged. Should be one of:
80
- # Logger::DEBUG (0), Logger::INFO (1), Logger::WARN (2), Logger::ERROR (2), Logger::FATAL (3)
81
- # @option args [String] :cookies Cookies to be sent with all requests in the format: name=value; name2=value2
82
- #
83
- # @return [JenkinsApi::Client] a client object to Jenkins API
84
- #
85
- # @raise [ArgumentError] when required options are not provided.
86
- #
87
- def initialize(args)
88
- args = symbolize_keys(args)
89
- args.each do |key, value|
90
- if value && VALID_PARAMS.include?(key.to_s)
91
- instance_variable_set("@#{key}", value)
92
- end
93
- end if args.is_a? Hash
94
-
95
- # Server IP or Server URL must be specific
96
- unless @server_ip || @server_url
97
- raise ArgumentError, "Server IP or Server URL is required to connect" +
98
- " to Jenkins"
99
- end
100
-
101
- # Get info from the server_url, if we got one
102
- if @server_url
103
- server_uri = URI.parse(@server_url)
104
- @server_ip = server_uri.host
105
- @server_port = server_uri.port
106
- @ssl = server_uri.scheme == "https"
107
- @jenkins_path = server_uri.path
108
-
109
- # read username and password from the URL
110
- # only set if @username and @password are not already set via explicit options
111
- @username ||= server_uri.user
112
- @password ||= server_uri.password
113
- end
114
-
115
- # Username/password are optional as some jenkins servers do not require
116
- # authentication
117
- if @username && !(@password || @password_base64)
118
- raise ArgumentError, "If username is provided, password is required"
119
- end
120
- if @proxy_ip.nil? ^ @proxy_port.nil?
121
- raise ArgumentError, "Proxy IP and port must both be specified or" +
122
- " both left nil"
123
- end
124
-
125
- @jenkins_path ||= ""
126
- @jenkins_path.gsub!(/\/$/, "") # remove trailing slash if there is one
127
- @server_port = DEFAULT_SERVER_PORT unless @server_port
128
- @timeout = DEFAULT_TIMEOUT unless @timeout
129
- @http_open_timeout = DEFAULT_HTTP_OPEN_TIMEOUT unless @http_open_timeout
130
- @http_read_timeout = DEFAULT_HTTP_READ_TIMEOUT unless @http_read_timeout
131
- @ssl ||= false
132
- @proxy_protocol ||= 'http'
133
-
134
- # Setting log options
135
- if @logger
136
- raise ArgumentError, "logger parameter must be a Logger object" unless @logger.is_a?(Logger)
137
- raise ArgumentError, "log_level should not be set if using custom logger" if @log_level
138
- raise ArgumentError, "log_location should not be set if using custom logger" if @log_location
139
- else
140
- @log_location = STDOUT unless @log_location
141
- @log_level = Logger::INFO unless @log_level
142
- @logger = Logger.new(@log_location)
143
- @logger.level = @log_level
144
- end
145
-
146
- # Base64 decode inserts a newline character at the end. As a workaround
147
- # added chomp to remove newline characters. I hope nobody uses newline
148
- # characters at the end of their passwords :)
149
- @password = Base64.decode64(@password_base64).chomp if @password_base64
150
-
151
- # No connections are made to the Jenkins server during initialize to
152
- # allow the unit tests to behave normally as mocking is simpler this way.
153
- # If this variable is nil, the first POST request will query the API and
154
- # populate this variable.
155
- @crumbs_enabled = nil
156
- # The crumbs hash. Store it so that we don't have to obtain the crumb for
157
- # every POST request. It appears that the crumb doesn't change often.
158
- @crumb = {}
159
- # This is the number of times to refetch the crumb if it ever expires.
160
- @crumb_max_retries = 3
161
- @header = {}
162
- end
163
-
164
- # Creates an instance to the Job class by passing a reference to self
165
- #
166
- # @return [JenkinsApi::Client::Job] An object to Job subclass
167
- #
168
- def job
169
- Lhj::JenkinsApi::Client::Job.new(self)
170
- end
171
-
172
- # Creates an instance to the System class by passing a reference to self
173
- #
174
- # @return [JenkinsApi::Client::System] An object to System subclass
175
- #
176
- def system
177
- Lhj::JenkinsApi::Client::System.new(self)
178
- end
179
-
180
- # Creates an instance to the Node class by passing a reference to self
181
- #
182
- # @return [JenkinsApi::Client::Node] An object to Node subclass
183
- #
184
- def node
185
- Lhj::JenkinsApi::Client::Node.new(self)
186
- end
187
-
188
- # Creates an instance to the View class by passing a reference to self
189
- #
190
- # @return [JenkinsApi::Client::View] An object to View subclass
191
- #
192
- def view
193
- Lhj::JenkinsApi::Client::View.new(self)
194
- end
195
-
196
- # Creates an instance to the BuildQueue by passing a reference to self
197
- #
198
- # @return [JenkinsApi::Client::BuildQueue] An object to BuildQueue subclass
199
- #
200
- def queue
201
- Lhj::JenkinsApi::Client::BuildQueue.new(self)
202
- end
203
-
204
- # Creates an instance to the PluginManager by passing a reference to self
205
- #
206
- # @return [JenkinsApi::Client::PluginManager] an object to PluginManager
207
- # subclass
208
- #
209
- def plugin
210
- Lhj::JenkinsApi::Client::PluginManager.new(self)
211
- end
212
-
213
- # Creates an instance of the User class by passing a reference to self
214
- #
215
- # @return [JenkinsApi::Client::User] An object of User subclass
216
- #
217
- def user
218
- Lhj::JenkinsApi::Client::User.new(self)
219
- end
220
-
221
- # Creates an instance of the Root class by passing a reference to self
222
- #
223
- # @return [JenkinsApi::Client::Root] An object of Root subclass
224
- #
225
- def root
226
- Lhj::JenkinsApi::Client::Root.new(self)
227
- end
228
-
229
- # Returns a string representing the class name
230
- #
231
- # @return [String] string representation of class name
232
- #
233
- def to_s
234
- "#<JenkinsApi::Client>"
235
- end
236
-
237
- # Overrides the inspect method to get rid of the credentials being shown in
238
- # the in interactive IRB sessions and also when the `inspect` method is
239
- # called. Just print the important variables.
240
- #
241
- def inspect
242
- "#<JenkinsApi::Client:0x#{(self.__id__ * 2).to_s(16)}" +
243
- " @ssl=#{@ssl.inspect}," +
244
- " @ca_file=#{@ca_file.inspect}," +
245
- " @log_location=#{@log_location.inspect}," +
246
- " @log_level=#{@log_level.inspect}," +
247
- " @crumbs_enabled=#{@crumbs_enabled.inspect}," +
248
- " @follow_redirects=#{@follow_redirects.inspect}," +
249
- " @jenkins_path=#{@jenkins_path.inspect}," +
250
- " @timeout=#{@timeout.inspect}>," +
251
- " @http_open_timeout=#{@http_open_timeout.inspect}>," +
252
- " @http_read_timeout=#{@http_read_timeout.inspect}>"
253
- end
254
-
255
- # Connects to the server and downloads artifacts to a specified location
256
- #
257
- # @param [String] job_name
258
- # @param [String] filename location to save artifact
259
- #
260
- def get_artifact(job_name, filename)
261
- @artifact = job.find_artifact(job_name)
262
- response = make_http_request(Net::HTTP::Get.new(@artifact))
263
- if response.code == "200"
264
- File.write(File.expand_path(filename), response.body)
265
- else
266
- raise "Couldn't get the artifact"
267
- end
268
- end
269
-
270
- # Connects to the server and download all artifacts of a build to a specified location
271
- #
272
- # @param [String] job_name
273
- # @param [String] dldir location to save artifacts
274
- # @param [Integer] build_number optional, defaults to current build
275
- # @returns [String, Array] list of retrieved artifacts
276
- #
277
- def get_artifacts(job_name, dldir, build_number = nil)
278
- @artifacts = job.find_artifacts(job_name, build_number)
279
- results = []
280
- @artifacts.each do |artifact|
281
- uri = URI.parse(artifact)
282
- http = Net::HTTP.new(uri.host, uri.port)
283
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
284
- http.use_ssl = @ssl
285
- request = Net::HTTP::Get.new(uri.request_uri)
286
- request.basic_auth(@username, @password)
287
- response = http.request(request)
288
- # we want every thing after the last 'build' in the path to become the filename
289
- if artifact.include?('/build/')
290
- filename = artifact.split("/build/").last.gsub('/', '-')
291
- else
292
- filename = File.basename(artifact)
293
- end
294
- filename = File.join(dldir, filename)
295
- results << filename
296
- if response.code == "200"
297
- File.write(File.expand_path(filename), response.body)
298
- else
299
- raise "Couldn't get the artifact #{artifact} for job #{job}"
300
- end
301
- end
302
- results
303
- end
304
-
305
- # Connects to the Jenkins server, sends the specified request and returns
306
- # the response.
307
- #
308
- # @param [Net::HTTPRequest] request The request object to send
309
- # @param [Boolean] follow_redirect whether to follow redirects or not
310
- #
311
- # @return [Net::HTTPResponse] Response from Jenkins
312
- #
313
- def make_http_request(request, follow_redirect = @follow_redirects)
314
- request.basic_auth @username, @password if @username
315
- request['Cookie'] = @cookies if @cookies
316
-
317
- if @proxy_ip
318
- case @proxy_protocol
319
- when 'http'
320
- http = Net::HTTP::Proxy(@proxy_ip, @proxy_port).new(@server_ip, @server_port)
321
- when 'socks'
322
- http = Net::HTTP::SOCKSProxy(@proxy_ip, @proxy_port).start(@server_ip, @server_port)
323
- else
324
- raise "unknown proxy protocol: '#{@proxy_protocol}'"
325
- end
326
- else
327
- http = Net::HTTP.new(@server_ip, @server_port)
328
- end
329
-
330
- if @ssl && @pkcs_file_path
331
- http.use_ssl = true
332
- pkcs12 = OpenSSL::PKCS12.new(File.binread(@pkcs_file_path), @pass_phrase != nil ? @pass_phrase : "")
333
- http.cert = pkcs12.certificate
334
- http.key = pkcs12.key
335
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
336
- elsif @ssl
337
- http.use_ssl = true
338
-
339
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
340
- http.ca_file = @ca_file if @ca_file
341
- end
342
- http.open_timeout = @http_open_timeout
343
- http.read_timeout = @http_read_timeout
344
-
345
- response = http.request(request)
346
- case response
347
- when Net::HTTPRedirection then
348
- # If we got a redirect request, follow it (if flag set), but don't
349
- # go any deeper (only one redirect supported - don't want to follow
350
- # our tail)
351
- if follow_redirect
352
- redir_uri = URI.parse(response['location'])
353
- response = make_http_request(
354
- Net::HTTP::Get.new(redir_uri.path, false)
355
- )
356
- end
357
- end
358
-
359
- # Pick out some useful header info before we return
360
- @jenkins_version = response['X-Jenkins']
361
- @hudson_version = response['X-Hudson']
362
-
363
- return response
364
- end
365
-
366
- protected :make_http_request
367
-
368
- # Obtains the root of Jenkins server. This function is used to see if
369
- # Jenkins is running
370
- #
371
- # @return [Net::HTTP::Response] Response from Jenkins for "/"
372
- #
373
- def get_root
374
- @logger.debug "GET #{@jenkins_path}/"
375
- request = Net::HTTP::Get.new("#{@jenkins_path}/")
376
- make_http_request(request)
377
- end
378
-
379
- # Sends a GET request to the Jenkins CI server with the specified URL
380
- #
381
- # @param [String] url_prefix The prefix to use in the URL
382
- # @param [String] tree A specific JSON tree to optimize the API call
383
- # @param [String] url_suffix The suffix to be used in the URL
384
- # @param [Boolean] raw_response Return complete Response object instead of
385
- # JSON body of response
386
- #
387
- # @return [String, Hash] JSON response from Jenkins
388
- #
389
- def api_get_request(url_prefix, tree = nil, url_suffix = "/api/json",
390
- raw_response = false)
391
- url_prefix = "#{@jenkins_path}#{url_prefix}"
392
- to_get = ""
393
- if tree
394
- to_get = "#{url_prefix}#{url_suffix}?#{tree}"
395
- else
396
- to_get = "#{url_prefix}#{url_suffix}"
397
- end
398
- request = Net::HTTP::Get.new(to_get)
399
- @logger.debug "GET #{to_get}"
400
- response = make_http_request(request)
401
- if raw_response
402
- handle_exception(response, "raw")
403
- else
404
- handle_exception(response, "body", url_suffix =~ /json/)
405
- end
406
- end
407
-
408
- # Sends a POST message to the Jenkins CI server with the specified URL
409
- #
410
- # @param [String] url_prefix The prefix to be used in the URL
411
- # @param [Hash] form_data Form data to send with POST request
412
- # @param [Boolean] raw_response Return complete Response object instead of
413
- # JSON body of response
414
- #
415
- # @return [String] Response code form Jenkins Response
416
- #
417
- def api_post_request(url_prefix, form_data = {}, raw_response = false)
418
- retries = @crumb_max_retries
419
- begin
420
- refresh_crumbs
421
-
422
- # Added form_data default {} instead of nil to help with proxies
423
- # that barf with empty post
424
- request = Net::HTTP::Post.new("#{@jenkins_path}#{url_prefix}")
425
- @logger.debug "POST #{url_prefix}"
426
- if @crumbs_enabled
427
- request[@crumb["crumbRequestField"]] = @crumb["crumb"]
428
- request['Cookie'] = @header['set-cookie'] if @header
429
- end
430
- request.set_form_data(form_data)
431
- response = make_http_request(request)
432
- if raw_response
433
- handle_exception(response, "raw")
434
- else
435
- handle_exception(response)
436
- end
437
- rescue Exceptions::ForbiddenException => e
438
- refresh_crumbs(true)
439
-
440
- if @crumbs_enabled
441
- @logger.info "Retrying: #{@crumb_max_retries - retries + 1} out of" +
442
- " #{@crumb_max_retries} times..."
443
- retries -= 1
444
-
445
- if retries > 0
446
- retry
447
- else
448
- raise Exceptions::ForbiddenWithCrumb.new(@logger, e.message)
449
- end
450
- else
451
- raise
452
- end
453
- end
454
- end
455
-
456
- # Obtains the configuration of a component from the Jenkins CI server
457
- #
458
- # @param [String] url_prefix The prefix to be used in the URL
459
- #
460
- # @return [String] XML configuration obtained from Jenkins
461
- #
462
- def get_config(url_prefix)
463
- request = Net::HTTP::Get.new("#{@jenkins_path}#{url_prefix}/config.xml")
464
- @logger.debug "GET #{url_prefix}/config.xml"
465
- response = make_http_request(request)
466
- handle_exception(response, "body")
467
- end
468
-
469
- # Posts the given xml configuration to the url given
470
- #
471
- # @param [String] url_prefix The prefix to be used in the URL
472
- # @param [String] xml The XML configuration to be sent to Jenkins
473
- #
474
- # @return [String] Response code returned from Jenkins
475
- #
476
- def post_config(url_prefix, xml)
477
- post_data(url_prefix, xml, 'application/xml;charset=UTF-8')
478
- end
479
-
480
- def post_json(url_prefix, json)
481
- post_data(url_prefix, json, 'application/json;charset=UTF-8')
482
- end
483
-
484
- def post_data(url_prefix, data, content_type)
485
- retries = @crumb_max_retries
486
- begin
487
- refresh_crumbs
488
-
489
- request = Net::HTTP::Post.new("#{@jenkins_path}#{url_prefix}")
490
- @logger.debug "POST #{url_prefix}"
491
- request.body = data
492
- request.content_type = content_type
493
- if @crumbs_enabled
494
- request[@crumb["crumbRequestField"]] = @crumb["crumb"]
495
- end
496
- response = make_http_request(request)
497
- handle_exception(response)
498
- rescue Exceptions::ForbiddenException => e
499
- refresh_crumbs(true)
500
-
501
- if @crumbs_enabled
502
- @logger.info "Retrying: #{@crumb_max_retries - retries + 1} out of" +
503
- " #{@crumb_max_retries} times..."
504
- retries -= 1
505
-
506
- if retries > 0
507
- retry
508
- else
509
- raise Exceptions::ForbiddenWithCrumb.new(@logger, e.message)
510
- end
511
- else
512
- raise
513
- end
514
- end
515
- end
516
-
517
- def init_update_center
518
- @logger.info "Initializing Jenkins Update Center..."
519
- @logger.debug "Obtaining the JSON data for Update Center..."
520
- # TODO: Clean me up
521
- update_center_data = open("https://updates.jenkins.io/current/update-center.json").read
522
- # The Jenkins mirror returns the data in the following format
523
- # updateCenter.post(
524
- # {.. JSON data...}
525
- # );
526
- # which is used by the Javascript used by the Jenkins UI to send to Jenkins.
527
- #
528
- update_center_data.gsub!("updateCenter.post(\n", "")
529
- update_center_data.gsub!("\n);", "")
530
-
531
- @logger.debug "Posting the obtained JSON to Jenkins Update Center..."
532
- post_json("/updateCenter/byId/default/postBack", update_center_data)
533
- end
534
-
535
- # Checks if Jenkins uses crumbs (i.e) the XSS disable option is checked in
536
- # Jenkins' security settings
537
- #
538
- # @return [Boolean] whether Jenkins uses crumbs or not
539
- #
540
- def use_crumbs?
541
- response = api_get_request("", "tree=useCrumbs")
542
- response["useCrumbs"]
543
- end
544
-
545
- # Checks if Jenkins uses security
546
- #
547
- # @return [Boolean] whether Jenkins uses security or not
548
- #
549
- def use_security?
550
- response = api_get_request("", "tree=useSecurity")
551
- response["useSecurity"]
552
- end
553
-
554
- # Obtains the jenkins version from the API
555
- # Only queries Jenkins if the version is not already stored.
556
- # Note that the version is auto-updated after every request made to Jenkins
557
- # since it is returned as a header in every response
558
- #
559
- # @return [String] Jenkins version
560
- #
561
- def get_jenkins_version
562
- get_root if @jenkins_version.nil?
563
- @jenkins_version
564
- end
565
-
566
- # Obtain the Hudson version of the CI server
567
- # Only queries Hudson/Jenkins if the version is not already stored.
568
- # Note that the version is auto-updated after every request made to Jenkins
569
- # since it is returned as a header in every response
570
- #
571
- # @return [String] Version of Hudson on Jenkins server
572
- #
573
- def get_hudson_version
574
- get_root if @hudson_version.nil?
575
- @hudson_version
576
- end
577
-
578
- # Converts a version string to a list of integers
579
- # This makes it easier to compare versions since in 'version-speak',
580
- # v 1.2 is a lot older than v 1.102 - and simple < > on version
581
- # strings doesn't work so well
582
- def deconstruct_version_string(version)
583
- match = version.match(/^(\d+)\.(\d+)(?:\.(\d+))?$/)
584
-
585
- # Match should have 4 parts [0] = input string, [1] = major
586
- # [2] = minor, [3] = patch (possibly blank)
587
- if match && match.size == 4
588
- return [match[1].to_i, match[2].to_i, match[3].to_i || 0]
589
- else
590
- return nil
591
- end
592
- end
593
-
594
- # Compare two version strings (A and B)
595
- # if A == B, returns 0
596
- # if A > B, returns 1
597
- # if A < B, returns -1
598
- def compare_versions(version_a, version_b)
599
- if version_a == version_b
600
- return 0
601
- else
602
- version_a_d = deconstruct_version_string(version_a)
603
- version_b_d = deconstruct_version_string(version_b)
604
-
605
- if version_a_d[0] > version_b_d[0] ||
606
- (version_a_d[0] == version_b_d[0] && version_a_d[1] > version_b_d[1]) ||
607
- (version_a_d[0] == version_b_d[0] && version_a_d[1] == version_b_d[1] && version_a_d[2] > version_b_d[2])
608
- return 1
609
- else
610
- return -1
611
- end
612
- end
613
- end
614
-
615
- # Obtain the date of the Jenkins server
616
- #
617
- # @return [String] Server date
618
- #
619
- def get_server_date
620
- response = get_root
621
- response["Date"]
622
- end
623
-
624
- # Executes the provided groovy script on the Jenkins CI server
625
- #
626
- # @param [String] script_text The text of the groovy script to execute
627
- #
628
- # @return [String] The output of the executed groovy script
629
- #
630
- def exec_script(script_text)
631
- response = api_post_request('/scriptText', { 'script' => script_text }, true)
632
- response.body
633
- end
634
-
635
- # Execute the Jenkins CLI
636
- #
637
- # @param command [String] command name
638
- # @param args [Array] the arguments for the command
639
- #
640
- # @return [String] command output from the CLI
641
- #
642
- # @raise [Exceptions::CLIException] if there are issues in running the
643
- # commands using CLI
644
- #
645
- def exec_cli(command, args = [])
646
- base_dir = File.dirname(__FILE__)
647
- server_url = "http://#{@server_ip}:#{@server_port}/#{@jenkins_path}"
648
- cmd = "java -jar #{base_dir}/../../java_deps/jenkins-cli.jar -s #{server_url}"
649
- cmd << " -i #{@identity_file}" if @identity_file && !@identity_file.empty?
650
- cmd << " #{command}"
651
- cmd << " --username #{@username} --password #{@password}" if @identity_file.nil? || @identity_file.empty?
652
- cmd << ' '
653
- cmd << args.join(' ')
654
- java_cmd = Mixlib::ShellOut.new(cmd)
655
-
656
- # Run the command
657
- java_cmd.run_command
658
- if java_cmd.stderr.empty?
659
- java_cmd.stdout.chomp
660
- else
661
- # The stderr has a stack trace of the Java program. We'll already have
662
- # a stack trace for Ruby. So just display a descriptive message for the
663
- # error thrown by the CLI.
664
- raise Exceptions::CLIException.new(
665
- @logger,
666
- java_cmd.stderr.split("\n").first
667
- )
668
- end
669
- end
670
-
671
- private
672
-
673
- # Obtains the crumb from Jenkins' crumb issuer
674
- #
675
- # @return [Hash<String, String>] the crumb response from Jenkins' crumb
676
- # issuer
677
- #
678
- # @raise Exceptions::CrumbNotFoundException if the crumb is not provided
679
- # (i.e) XSS disable option is not checked in Jenkins' security setting
680
- #
681
- def get_crumb
682
- begin
683
- @logger.debug "Obtaining crumb from the jenkins server"
684
- api_get_request("/crumbIssuer")
685
- rescue Exceptions::NotFoundException
686
- raise Exceptions::CrumbNotFoundException.new(
687
- @logger,
688
- "CSRF protection is not enabled on the server at the moment." +
689
- " Perhaps the client was initialized when the CSRF setting was" +
690
- " enabled. Please re-initialize the client."
691
- )
692
- end
693
- end
694
-
695
- # Used to determine whether crumbs are enabled, and populate/clear our
696
- # local crumb accordingly.
697
- #
698
- # @param force_refresh [Boolean] determines whether the check is
699
- # cursory or deeper. The default is cursory - i.e. if crumbs
700
- # enabled is 'nil' then figure out what to do, otherwise skip
701
- # If 'true' the method will check to see if the crumbs require-
702
- # ment has changed (by querying Jenkins), and updating crumb
703
- # (refresh, delete, create) as appropriate.
704
- #
705
- def refresh_crumbs(force_refresh = false)
706
- # Quick check to see if someone has changed XSS settings and not
707
- # restarted us
708
- if force_refresh || @crumbs_enabled.nil?
709
- old_crumbs_setting = @crumbs_enabled
710
- new_crumbs_setting = use_crumbs?
711
-
712
- if old_crumbs_setting != new_crumbs_setting
713
- @crumbs_enabled = new_crumbs_setting
714
- end
715
-
716
- # Get or clear crumbs setting appropriately
717
- # Works as refresh if crumbs still enabled
718
- if @crumbs_enabled
719
- if old_crumbs_setting
720
- @logger.info "Crumb expired. Refetching from the server."
721
- else
722
- @logger.info "Crumbs turned on. Fetching from the server."
723
- end
724
-
725
- @crumb = get_crumb if force_refresh || !old_crumbs_setting
726
- else
727
- if old_crumbs_setting
728
- @logger.info "Crumbs turned off. Clearing crumb."
729
- @crumb.clear
730
- end
731
- end
732
- end
733
- end
734
-
735
- # Private method. Converts keys passed in as strings into symbols.
736
- #
737
- # @param hash [Hash] Hash containing arguments to login to jenkins.
738
- #
739
- def symbolize_keys(hash)
740
- hash.inject({}) { |result, (key, value)|
741
- new_key = case key
742
- when String then
743
- key.to_sym
744
- else
745
- key
746
- end
747
- new_value = case value
748
- when Hash then
749
- symbolize_keys(value)
750
- else
751
- value
752
- end
753
- result[new_key] = new_value
754
- result
755
- }
756
- end
757
-
758
- # Private method that handles the exception and raises with proper error
759
- # message with the type of exception and returns the required values if no
760
- # exceptions are raised.
761
- #
762
- # @param [Net::HTTP::Response] response Response from Jenkins
763
- # @param [String] to_send What should be returned as a response. Allowed
764
- # values: "code", "body", and "raw".
765
- # @param [Boolean] send_json Boolean value used to determine whether to
766
- # load the JSON or send the response as is.
767
- #
768
- # @return [String, Hash] Response returned whether loaded JSON or raw
769
- # string
770
- #
771
- # @raise [Exceptions::Unauthorized] When invalid credentials are
772
- # provided to connect to Jenkins
773
- # @raise [Exceptions::NotFound] When the requested page on Jenkins is not
774
- # found
775
- # @raise [Exceptions::InternalServerError] When Jenkins returns a 500
776
- # Internal Server Error
777
- # @raise [Exceptions::ApiException] Any other exception returned from
778
- # Jenkins that are not categorized in the API Client.
779
- #
780
- def handle_exception(response, to_send = "code", send_json = false)
781
- msg = "HTTP Code: #{response.code}, Response Body: #{response.body}"
782
- @logger.debug msg
783
- case response.code.to_i
784
- # As of Jenkins version 1.519, the job builds return a 201 status code
785
- # with a Location HTTP header with the pointing the URL of the item in
786
- # the queue.
787
- when 200, 201, 302
788
- @header = response.instance_variable_get(:@header)
789
- if to_send == "body" && send_json
790
- return JSON.parse(response.body)
791
- elsif to_send == "body"
792
- return response.body
793
- elsif to_send == "code"
794
- return response.code
795
- elsif to_send == "raw"
796
- return response
797
- end
798
- when 400
799
- matched = response.body.match(/<p>(.*)<\/p>/)
800
- api_message = matched[1] unless matched.nil?
801
- @logger.debug "API message: #{api_message}"
802
- case api_message
803
- when /A job already exists with the name/
804
- raise Exceptions::JobAlreadyExists.new(@logger, api_message)
805
- when /A view already exists with the name/
806
- raise Exceptions::ViewAlreadyExists.new(@logger, api_message)
807
- when /Slave called .* already exists/
808
- raise Exceptions::NodeAlreadyExists.new(@logger, api_message)
809
- when /Nothing is submitted/
810
- raise Exceptions::NothingSubmitted.new(@logger, api_message)
811
- else
812
- raise Exceptions::ApiException.new(@logger, api_message)
813
- end
814
- when 401
815
- raise Exceptions::Unauthorized.new @logger
816
- when 403
817
- raise Exceptions::Forbidden.new @logger
818
- when 404
819
- raise Exceptions::NotFound.new @logger
820
- when 500
821
- matched = response.body.match(/Exception: (.*)<br>/)
822
- api_message = matched[1] unless matched.nil?
823
- @logger.debug "API message: #{api_message}"
824
- raise Exceptions::InternalServerError.new(@logger, api_message)
825
- when 503
826
- raise Exceptions::ServiceUnavailable.new @logger
827
- else
828
- raise Exceptions::ApiException.new(
829
- @logger,
830
- "Error code #{response.code}"
831
- )
832
- end
833
- end
834
-
835
- end
836
- end
837
- end