jenkins_api_client 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +2 -0
- data/CHANGELOG.md +30 -1
- data/CONTRIBUTORS.md +13 -0
- data/README.md +126 -66
- data/Vagrantfile +85 -0
- data/config/login.yml.example +4 -0
- data/jenkins_api_client.gemspec +12 -12
- data/lib/jenkins_api_client/build_queue.rb +34 -0
- data/lib/jenkins_api_client/cli/job.rb +4 -11
- data/lib/jenkins_api_client/client.rb +241 -79
- data/lib/jenkins_api_client/exceptions.rb +109 -21
- data/lib/jenkins_api_client/job.rb +143 -28
- data/lib/jenkins_api_client/node.rb +16 -8
- data/lib/jenkins_api_client/system.rb +21 -2
- data/lib/jenkins_api_client/version.rb +2 -2
- data/lib/jenkins_api_client/view.rb +18 -1
- data/spec/func_tests/client_spec.rb +9 -15
- data/spec/func_tests/job_spec.rb +190 -51
- data/spec/func_tests/node_spec.rb +29 -12
- data/spec/func_tests/spec_helper.rb +1 -7
- data/spec/func_tests/system_spec.rb +25 -6
- data/spec/func_tests/view_spec.rb +101 -34
- data/spec/unit_tests/build_queue_spec.rb +4 -0
- data/spec/unit_tests/client_spec.rb +2 -36
- data/spec/unit_tests/job_spec.rb +48 -5
- data/spec/unit_tests/node_spec.rb +4 -7
- data/spec/unit_tests/spec_helper.rb +1 -0
- data/spec/unit_tests/system_spec.rb +15 -2
- data/spec/unit_tests/view_spec.rb +11 -7
- data/travis/jenkins_config_with_crumb.xml +67 -0
- data/travis/setup_crumb.sh +11 -0
- data/travis/spec.yml +4 -0
- metadata +158 -140
@@ -34,6 +34,7 @@ module JenkinsApi
|
|
34
34
|
#
|
35
35
|
def initialize(client)
|
36
36
|
@client = client
|
37
|
+
@logger = @client.logger
|
37
38
|
end
|
38
39
|
|
39
40
|
# Returns a string representation of BuildQueue class.
|
@@ -45,6 +46,7 @@ module JenkinsApi
|
|
45
46
|
# Gives the number of jobs currently in the build queue
|
46
47
|
#
|
47
48
|
def size
|
49
|
+
@logger.info "Obtaining the number of tasks in build queue"
|
48
50
|
response_json = @client.api_get_request("/queue")
|
49
51
|
response_json["items"].size
|
50
52
|
end
|
@@ -52,6 +54,7 @@ module JenkinsApi
|
|
52
54
|
# Lists all tasks currently in the build queue
|
53
55
|
#
|
54
56
|
def list
|
57
|
+
@logger.info "Obtaining the tasks currently in the build queue"
|
55
58
|
response_json = @client.api_get_request("/queue")
|
56
59
|
tasks = []
|
57
60
|
response_json["items"].each do |item|
|
@@ -67,6 +70,8 @@ module JenkinsApi
|
|
67
70
|
# @return [Fixnum] age in seconds
|
68
71
|
#
|
69
72
|
def get_age(task_name)
|
73
|
+
@logger.info "Obtaining the age of task '#{task_name}' from the" +
|
74
|
+
" build queue"
|
70
75
|
age = nil
|
71
76
|
details = get_details(task_name)
|
72
77
|
unless details.empty?
|
@@ -82,6 +87,8 @@ module JenkinsApi
|
|
82
87
|
# @return [Hash] Queue details of the specified task/job
|
83
88
|
#
|
84
89
|
def get_details(task_name)
|
90
|
+
@logger.info "Obtaining the details of task '#{task_name}' from the" +
|
91
|
+
" build queue"
|
85
92
|
response_json = @client.api_get_request("/queue")
|
86
93
|
details = {}
|
87
94
|
response_json["items"].each do |item|
|
@@ -90,6 +97,17 @@ module JenkinsApi
|
|
90
97
|
details
|
91
98
|
end
|
92
99
|
|
100
|
+
# Obtain the item in the queue provided the ID of the task
|
101
|
+
#
|
102
|
+
# @param task_id [String] the ID of the task
|
103
|
+
#
|
104
|
+
# @return [Hash] the details of the item in the queue
|
105
|
+
#
|
106
|
+
def get_item_by_id(task_id)
|
107
|
+
@logger.info "Obtaining the details of task with ID '#{task_id}'"
|
108
|
+
@client.api_get_request("/queue/item/#{task_id}")
|
109
|
+
end
|
110
|
+
|
93
111
|
# Obtains the causes from the build queue for the specified task
|
94
112
|
#
|
95
113
|
# @param [String] task_name
|
@@ -97,6 +115,8 @@ module JenkinsApi
|
|
97
115
|
# @return [Array] causes for the task to be in queue
|
98
116
|
#
|
99
117
|
def get_causes(task_name)
|
118
|
+
@logger.info "Obtaining the causes of task '#{task_name}' from the" +
|
119
|
+
" build queue"
|
100
120
|
causes = nil
|
101
121
|
details = get_details(task_name)
|
102
122
|
unless details.empty?
|
@@ -115,6 +135,8 @@ module JenkinsApi
|
|
115
135
|
# @return [String] reason for being in queue, nil if no task found
|
116
136
|
#
|
117
137
|
def get_reason(task_name)
|
138
|
+
@logger.info "Obtaining the reason of task '#{task_name}' from the" +
|
139
|
+
" build queue"
|
118
140
|
reason = nil
|
119
141
|
details = get_details(task_name)
|
120
142
|
unless details.empty?
|
@@ -131,6 +153,8 @@ module JenkinsApi
|
|
131
153
|
# tasks with no ETA info.
|
132
154
|
#
|
133
155
|
def get_eta(task_name)
|
156
|
+
@logger.info "Obtaining the ETA for the task '#{task_name}' from the" +
|
157
|
+
" build queue"
|
134
158
|
eta = nil
|
135
159
|
details = get_details(task_name)
|
136
160
|
unless details.empty?
|
@@ -153,6 +177,8 @@ module JenkinsApi
|
|
153
177
|
# @return [String] ID of the task, nil of no task is found
|
154
178
|
#
|
155
179
|
def get_id(task_name)
|
180
|
+
@logger.info "Obtaining the ID of task '#{task_name}' from the" +
|
181
|
+
" build queue"
|
156
182
|
id = nil
|
157
183
|
details = get_details(task_name)
|
158
184
|
unless details.empty?
|
@@ -168,6 +194,8 @@ module JenkinsApi
|
|
168
194
|
# @return [String] params, nil if the no task is found
|
169
195
|
#
|
170
196
|
def get_params(task_name)
|
197
|
+
@logger.info "Obtaining the build parameters of task '#{task_name}'" +
|
198
|
+
" from the build queue"
|
171
199
|
params = nil
|
172
200
|
details = get_details(task_name)
|
173
201
|
unless details.empty?
|
@@ -183,6 +211,8 @@ module JenkinsApi
|
|
183
211
|
# @return [TrueClass|FalseClass] buildable or not
|
184
212
|
#
|
185
213
|
def is_buildable?(task_name)
|
214
|
+
@logger.info "Checking if task '#{task_name}' from the build queue" +
|
215
|
+
" is buildable"
|
186
216
|
buildable = nil
|
187
217
|
details = get_details(task_name)
|
188
218
|
unless details.empty?
|
@@ -198,6 +228,8 @@ module JenkinsApi
|
|
198
228
|
# @return [TrueClass|FalseClass] blocked or not
|
199
229
|
#
|
200
230
|
def is_blocked?(task_name)
|
231
|
+
@logger.info "Checking if task '#{task_name}' from the build queue" +
|
232
|
+
" is blocked"
|
201
233
|
blocked = nil
|
202
234
|
details = get_details(task_name)
|
203
235
|
unless details.empty?
|
@@ -213,6 +245,8 @@ module JenkinsApi
|
|
213
245
|
# @return [TrueClass|FalseClass] stuck or not
|
214
246
|
#
|
215
247
|
def is_stuck?(task_name)
|
248
|
+
@logger.info "Checking if task '#{task_name}' from the build queue" +
|
249
|
+
" is stuck"
|
216
250
|
stuck = nil
|
217
251
|
details = get_details(task_name)
|
218
252
|
unless details.empty?
|
@@ -89,20 +89,15 @@ module JenkinsApi
|
|
89
89
|
desc "console JOB", "Print the progressive console output of a job"
|
90
90
|
method_option :sleep, :aliases => "-z",
|
91
91
|
:desc => "Time to wait between querying the API for console output"
|
92
|
-
# CLI command to obtain console output for a job
|
92
|
+
# CLI command to obtain console output for a job. Make sure the log
|
93
|
+
# location is set to something other than STDOUT. By default it is set to
|
94
|
+
# STDOUT. If the log messages are printed on the same console, the
|
95
|
+
# console output will get garbled.
|
93
96
|
#
|
94
97
|
# @param [String] job Name of the job
|
95
98
|
#
|
96
99
|
def console(job)
|
97
100
|
@client = Helper.setup(parent_options)
|
98
|
-
# If debug is enabled, disable it. It shouldn't interfere
|
99
|
-
# with console output.
|
100
|
-
debug_changed = false
|
101
|
-
if @client.debug == true
|
102
|
-
@client.debug = false
|
103
|
-
debug_changed = true
|
104
|
-
end
|
105
|
-
|
106
101
|
# Print progressive console output
|
107
102
|
response = @client.job.get_console_output(job)
|
108
103
|
puts response['output'] unless response['more']
|
@@ -114,8 +109,6 @@ module JenkinsApi
|
|
114
109
|
end
|
115
110
|
# Print the last few lines
|
116
111
|
puts response['output'] unless response['output'].chomp.empty?
|
117
|
-
# Change the debug back if we changed it now
|
118
|
-
@client.toggle_debug if debug_changed
|
119
112
|
end
|
120
113
|
|
121
114
|
desc "restrict JOB", "Restricts a job to a specific node"
|
@@ -26,7 +26,9 @@ require 'net/http'
|
|
26
26
|
require 'net/https'
|
27
27
|
require 'nokogiri'
|
28
28
|
require 'base64'
|
29
|
-
require
|
29
|
+
require 'mixlib/shellout'
|
30
|
+
require 'uri'
|
31
|
+
require 'logger'
|
30
32
|
|
31
33
|
# The main module that contains the Client class and all subclasses that
|
32
34
|
# communicate with the Jenkins's Remote Access API.
|
@@ -37,7 +39,7 @@ module JenkinsApi
|
|
37
39
|
# for various operations.
|
38
40
|
#
|
39
41
|
class Client
|
40
|
-
attr_accessor :
|
42
|
+
attr_accessor :timeout, :logger
|
41
43
|
# Default port to be used to connect to Jenkins
|
42
44
|
DEFAULT_SERVER_PORT = 8080
|
43
45
|
# Default timeout in seconds to be used while performing operations
|
@@ -53,27 +55,54 @@ module JenkinsApi
|
|
53
55
|
"username",
|
54
56
|
"password",
|
55
57
|
"password_base64",
|
56
|
-
"
|
58
|
+
"log_location",
|
59
|
+
"log_level",
|
57
60
|
"timeout",
|
58
61
|
"ssl",
|
59
|
-
"follow_redirects"
|
62
|
+
"follow_redirects",
|
63
|
+
"identity_file"
|
60
64
|
].freeze
|
61
65
|
|
62
66
|
# Initialize a Client object with Jenkins CI server credentials
|
63
67
|
#
|
64
|
-
# @param [Hash]
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
68
|
+
# @param args [Hash] Arguments to connect to Jenkins server
|
69
|
+
#
|
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
|
82
|
+
# 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 http://#{server_ip}:#{server_port}/user/#{my_username}/configure
|
87
|
+
# @option args [String] :proxy_ip
|
88
|
+
# the proxy IP address
|
89
|
+
# @option args [String] :proxy_port
|
90
|
+
# the proxy port
|
91
|
+
# @option args [String] :jenkins_path
|
92
|
+
# the optional context path for Jenkins
|
93
|
+
# @option args [Boolean] :ssl
|
94
|
+
# indicates if Jenkins is accessible over HTTPS (defaults to false)
|
95
|
+
# @option args [Boolean] :follow_redirects
|
96
|
+
# This argument causes the client to follow a redirect (jenkins can
|
97
|
+
# return a 30x when starting a build)
|
98
|
+
# @option args [Fixnum] :timeout
|
99
|
+
# This argument sets the timeout for the jenkins system to become ready
|
100
|
+
# @option args [String] :log_location
|
101
|
+
# The location for the log file (Defaults to STDOUT)
|
102
|
+
# @option args [Fixnum] :log_level
|
103
|
+
# The level for messages to be logged. Should be one of:
|
104
|
+
# Logger::DEBUG (0), Logger::INFO (1), Logger::WARN (2), Logger::ERROR
|
105
|
+
# (2), Logger::FATAL (3) (Defaults to Logger::INFO)
|
77
106
|
#
|
78
107
|
# @return [JenkinsApi::Client] a client object to Jenkins API
|
79
108
|
#
|
@@ -85,10 +114,15 @@ module JenkinsApi
|
|
85
114
|
instance_variable_set("@#{key}", value)
|
86
115
|
end
|
87
116
|
end if args.is_a? Hash
|
117
|
+
|
118
|
+
# Server IP or Server URL must be specifiec
|
88
119
|
unless @server_ip || @server_url
|
89
|
-
raise ArgumentError, "Server IP or Server URL is required to connect
|
120
|
+
raise ArgumentError, "Server IP or Server URL is required to connect" +
|
121
|
+
" to Jenkins"
|
90
122
|
end
|
91
|
-
|
123
|
+
|
124
|
+
# Username/password are optional as some jenkins servers do not require
|
125
|
+
# authentication
|
92
126
|
if @username && !(@password || @password_base64)
|
93
127
|
raise ArgumentError, "If username is provided, password is required"
|
94
128
|
end
|
@@ -96,23 +130,44 @@ module JenkinsApi
|
|
96
130
|
raise ArgumentError, "Proxy IP and port must both be specified or" +
|
97
131
|
" both left nil"
|
98
132
|
end
|
99
|
-
|
133
|
+
|
134
|
+
# Get info from the server_url, if we got one
|
135
|
+
if @server_url
|
136
|
+
server_uri = URI.parse(@server_url)
|
137
|
+
@server_ip = server_uri.host
|
138
|
+
@server_port = server_uri.port
|
139
|
+
@ssl = server_uri.scheme == "https"
|
140
|
+
@jenkins_path = server_uri.path
|
141
|
+
end
|
142
|
+
|
143
|
+
@jenkins_path ||= ""
|
144
|
+
@jenkins_path.gsub!(/\/$/,"") # remove trailing slash if there is one
|
100
145
|
@server_port = DEFAULT_SERVER_PORT unless @server_port
|
101
146
|
@timeout = DEFAULT_TIMEOUT unless @timeout
|
102
147
|
@ssl ||= false
|
103
|
-
|
104
|
-
|
148
|
+
|
149
|
+
# Setting log options
|
150
|
+
@log_location = STDOUT unless @log_location
|
151
|
+
@log_level = Logger::INFO unless @log_level
|
152
|
+
@logger = Logger.new(@log_location)
|
153
|
+
@logger.level = @log_level
|
154
|
+
|
105
155
|
|
106
156
|
# Base64 decode inserts a newline character at the end. As a workaround
|
107
157
|
# added chomp to remove newline characters. I hope nobody uses newline
|
108
158
|
# characters at the end of their passwords :)
|
109
159
|
@password = Base64.decode64(@password_base64).chomp if @password_base64
|
110
|
-
end
|
111
160
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
161
|
+
# No connections are made to the Jenkins server during initialize to
|
162
|
+
# allow the unit tests to behave normally as mocking is simpler this way.
|
163
|
+
# If this variable is nil, the first POST request will query the API and
|
164
|
+
# populate this variable.
|
165
|
+
@crumbs_enabled = nil
|
166
|
+
# The crumbs hash. Store it so that we don't have to obtain the crumb for
|
167
|
+
# every POST request. It appears that the crumb doesn't change often.
|
168
|
+
@crumb = {}
|
169
|
+
# This is the number of times to refetch the crumb if it ever expires.
|
170
|
+
@crumb_max_retries = 3
|
116
171
|
end
|
117
172
|
|
118
173
|
# Creates an instance to the Job class by passing a reference to self
|
@@ -163,6 +218,21 @@ module JenkinsApi
|
|
163
218
|
"#<JenkinsApi::Client>"
|
164
219
|
end
|
165
220
|
|
221
|
+
# Overrides the inspect method to get rid of the credentials being shown in
|
222
|
+
# the in interactive IRB sessions and also when the `inspect` method is
|
223
|
+
# called. Just print the important variables.
|
224
|
+
#
|
225
|
+
def inspect
|
226
|
+
"#<JenkinsApi::Client:0x#{(self.__id__ * 2).to_s(16)}" +
|
227
|
+
" @ssl=#{@ssl.inspect}," +
|
228
|
+
" @log_location=#{@log_location.inspect}," +
|
229
|
+
" @log_level=#{@log_level.inspect}," +
|
230
|
+
" @crumbs_enabled=#{@crumbs_enabled.inspect}," +
|
231
|
+
" @follow_redirects=#{@follow_redirects.inspect}," +
|
232
|
+
" @jenkins_path=#{@jenkins_path.inspect}," +
|
233
|
+
" @timeout=#{@timeout.inspect}>"
|
234
|
+
end
|
235
|
+
|
166
236
|
# Connects to the Jenkins server, sends the specified request and returns
|
167
237
|
# the response.
|
168
238
|
#
|
@@ -174,18 +244,10 @@ module JenkinsApi
|
|
174
244
|
def make_http_request(request, follow_redirect = @follow_redirects)
|
175
245
|
request.basic_auth @username, @password if @username
|
176
246
|
|
177
|
-
if @server_uri
|
178
|
-
host = @server_uri.host
|
179
|
-
port = @server_uri.port
|
180
|
-
else
|
181
|
-
host = @server_ip
|
182
|
-
port = @server_port
|
183
|
-
end
|
184
|
-
|
185
247
|
if @proxy_ip
|
186
|
-
http = Net::HTTP::Proxy(@proxy_ip, @proxy_port).new(
|
248
|
+
http = Net::HTTP::Proxy(@proxy_ip, @proxy_port).new(@server_ip, @server_port)
|
187
249
|
else
|
188
|
-
http = Net::HTTP.new(
|
250
|
+
http = Net::HTTP.new(@server_ip, @server_port)
|
189
251
|
end
|
190
252
|
|
191
253
|
if @ssl
|
@@ -216,6 +278,7 @@ module JenkinsApi
|
|
216
278
|
# @return [Net::HTTP::Response] Response from Jenkins for "/"
|
217
279
|
#
|
218
280
|
def get_root
|
281
|
+
@logger.info "GET /"
|
219
282
|
request = Net::HTTP::Get.new("/")
|
220
283
|
make_http_request(request)
|
221
284
|
end
|
@@ -241,10 +304,10 @@ module JenkinsApi
|
|
241
304
|
end
|
242
305
|
to_get = URI.escape(to_get)
|
243
306
|
request = Net::HTTP::Get.new(to_get)
|
244
|
-
|
307
|
+
@logger.info "GET #{to_get}"
|
245
308
|
response = make_http_request(request)
|
246
309
|
if raw_response
|
247
|
-
response
|
310
|
+
handle_exception(response, "raw")
|
248
311
|
else
|
249
312
|
handle_exception(response, "body", url_suffix =~ /json/)
|
250
313
|
end
|
@@ -257,16 +320,37 @@ module JenkinsApi
|
|
257
320
|
#
|
258
321
|
# @return [String] Response code form Jenkins Response
|
259
322
|
#
|
260
|
-
def api_post_request(url_prefix, form_data = {})
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
323
|
+
def api_post_request(url_prefix, form_data = {}, raw_response = false)
|
324
|
+
retries = @crumb_max_retries
|
325
|
+
begin
|
326
|
+
# Identify whether to use crumbs if this is the first POST request.
|
327
|
+
@crumbs_enabled = use_crumbs? if @crumbs_enabled.nil?
|
328
|
+
@crumb = get_crumb if @crumbs_enabled && @crumb.empty?
|
329
|
+
|
330
|
+
# Added form_data default {} instead of nil to help with proxies
|
331
|
+
# that barf with empty post
|
332
|
+
url_prefix = URI.escape("#{@jenkins_path}#{url_prefix}")
|
333
|
+
request = Net::HTTP::Post.new("#{url_prefix}")
|
334
|
+
@logger.info "POST #{url_prefix}"
|
335
|
+
request.content_type = 'application/json'
|
336
|
+
if @crumbs_enabled
|
337
|
+
request[@crumb["crumbRequestField"]] = @crumb["crumb"]
|
338
|
+
end
|
339
|
+
request.set_form_data(form_data)
|
340
|
+
response = make_http_request(request)
|
341
|
+
if raw_response
|
342
|
+
handle_exception(response, "raw")
|
343
|
+
else
|
344
|
+
handle_exception(response)
|
345
|
+
end
|
346
|
+
rescue Exceptions::ForbiddenException
|
347
|
+
@logger.info "Crumb expired. Refetching from the server. Trying" +
|
348
|
+
" #{@crumb_max_retries - retries + 1} out of #{@crumb_max_retries}" +
|
349
|
+
" times..."
|
350
|
+
@crumb = get_crumb
|
351
|
+
retries -= 1
|
352
|
+
retries > 0 ? retry : raise
|
353
|
+
end
|
270
354
|
end
|
271
355
|
|
272
356
|
# Obtains the configuration of a component from the Jenkins CI server
|
@@ -278,7 +362,7 @@ module JenkinsApi
|
|
278
362
|
def get_config(url_prefix)
|
279
363
|
url_prefix = URI.escape("#{@jenkins_path}#{url_prefix}")
|
280
364
|
request = Net::HTTP::Get.new("#{url_prefix}/config.xml")
|
281
|
-
|
365
|
+
@logger.info "GET #{url_prefix}/config.xml"
|
282
366
|
response = make_http_request(request)
|
283
367
|
handle_exception(response, "body")
|
284
368
|
end
|
@@ -291,18 +375,54 @@ module JenkinsApi
|
|
291
375
|
# @return [String] Response code returned from Jenkins
|
292
376
|
#
|
293
377
|
def post_config(url_prefix, xml)
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
378
|
+
retries = @crumb_max_retries
|
379
|
+
begin
|
380
|
+
# Identify whether to use crumbs if this is the first POST request.
|
381
|
+
@crumbs_enabled = use_crumbs? if @crumbs_enabled.nil?
|
382
|
+
@crumb = get_crumb if @crumbs_enabled && @crumb.empty?
|
383
|
+
|
384
|
+
url_prefix = URI.escape("#{@jenkins_path}#{url_prefix}")
|
385
|
+
request = Net::HTTP::Post.new("#{url_prefix}")
|
386
|
+
@logger.info "POST #{url_prefix}"
|
387
|
+
request.body = xml
|
388
|
+
request.content_type = 'application/xml'
|
389
|
+
if @crumbs_enabled
|
390
|
+
request[@crumb["crumbRequestField"]] = @crumb["crumb"]
|
391
|
+
end
|
392
|
+
response = make_http_request(request)
|
393
|
+
handle_exception(response)
|
394
|
+
rescue Exceptions::ForbiddenException
|
395
|
+
@logger.info "Crumb expired. Refetching from the server. Trying" +
|
396
|
+
" #{@crumb_max_retries - retries + 1} out of #{@crumb_max_retries}" +
|
397
|
+
" times..."
|
398
|
+
@crumb = get_crumb
|
399
|
+
retries -= 1
|
400
|
+
retries > 0 ? retry : raise
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# Checks if Jenkins uses crumbs (i.e) the XSS disable option is checked in
|
405
|
+
# Jenkins' security settings
|
406
|
+
#
|
407
|
+
# @return [Boolean] whether Jenkins uses crumbs or not
|
408
|
+
#
|
409
|
+
def use_crumbs?
|
410
|
+
response = api_get_request("")
|
411
|
+
response["useCrumbs"]
|
301
412
|
end
|
302
413
|
|
303
|
-
#
|
414
|
+
# Checks if Jenkins uses security
|
415
|
+
#
|
416
|
+
# @return [Boolean] whether Jenkins uses security or not
|
304
417
|
#
|
305
|
-
|
418
|
+
def use_security?
|
419
|
+
response = api_get_request("")
|
420
|
+
response["useSecurity"]
|
421
|
+
end
|
422
|
+
|
423
|
+
# Obtains the jenkins version from the API
|
424
|
+
#
|
425
|
+
# @return Jenkins version
|
306
426
|
#
|
307
427
|
def get_jenkins_version
|
308
428
|
response = get_root
|
@@ -340,10 +460,11 @@ module JenkinsApi
|
|
340
460
|
def exec_cli(command, args = [])
|
341
461
|
base_dir = File.dirname(__FILE__)
|
342
462
|
server_url = "http://#{@server_ip}:#{@server_port}/#{@jenkins_path}"
|
343
|
-
cmd = "java -jar #{base_dir}/../../java_deps/jenkins-cli.jar"
|
344
|
-
|
345
|
-
|
346
|
-
|
463
|
+
cmd = "java -jar #{base_dir}/../../java_deps/jenkins-cli.jar -s #{server_url}"
|
464
|
+
cmd << " -i #{@identity_file}" if @identity_file && !@identity_file.empty?
|
465
|
+
cmd << " #{command}"
|
466
|
+
cmd << " --username #{@username} --password #{@password} " if @identity_file.nil? || @identity_file.empty?
|
467
|
+
cmd << args.join(' ')
|
347
468
|
java_cmd = Mixlib::ShellOut.new(cmd)
|
348
469
|
|
349
470
|
# Run the command
|
@@ -354,37 +475,62 @@ module JenkinsApi
|
|
354
475
|
# The stderr has a stack trace of the Java program. We'll already have
|
355
476
|
# a stack trace for Ruby. So just display a descriptive message for the
|
356
477
|
# error thrown by the CLI.
|
357
|
-
raise Exceptions::CLIException.new(
|
478
|
+
raise Exceptions::CLIException.new(
|
479
|
+
@logger,
|
480
|
+
java_cmd.stderr.split("\n").first
|
481
|
+
)
|
358
482
|
end
|
359
483
|
end
|
360
484
|
|
361
485
|
private
|
362
486
|
|
487
|
+
# Obtains the crumb from Jenkins' crumb issuer
|
488
|
+
#
|
489
|
+
# @return [Hash<String, String>] the crumb response from Jenkins' crumb
|
490
|
+
# issuer
|
491
|
+
#
|
492
|
+
# @raise Exceptions::CrumbNotFoundException if the crumb is not provided
|
493
|
+
# (i.e) XSS disable option is not checked in Jenkins' security setting
|
494
|
+
#
|
495
|
+
def get_crumb
|
496
|
+
begin
|
497
|
+
@logger.debug "Obtaining crumb from the jenkins server"
|
498
|
+
api_get_request("/crumbIssuer")
|
499
|
+
rescue Exceptions::NotFoundException
|
500
|
+
raise Exceptions::CrumbNotFoundException.new(
|
501
|
+
@logger,
|
502
|
+
"CSRF protection is not enabled on the server at the moment." +
|
503
|
+
" Perhaps the client was initialized when the CSRF setting was" +
|
504
|
+
" enabled. Please re-initialize the client."
|
505
|
+
)
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
363
509
|
# Private method that handles the exception and raises with proper error
|
364
510
|
# message with the type of exception and returns the required values if no
|
365
511
|
# exceptions are raised.
|
366
512
|
#
|
367
513
|
# @param [Net::HTTP::Response] response Response from Jenkins
|
368
514
|
# @param [String] to_send What should be returned as a response. Allowed
|
369
|
-
# values: "code" and "
|
515
|
+
# values: "code", "body", and "raw".
|
370
516
|
# @param [Boolean] send_json Boolean value used to determine whether to
|
371
517
|
# load the JSON or send the response as is.
|
372
518
|
#
|
373
519
|
# @return [String, JSON] Response returned whether loaded JSON or raw
|
374
520
|
# string
|
375
521
|
#
|
376
|
-
# @raise [Exceptions::
|
522
|
+
# @raise [Exceptions::Unauthorized] When invalid credentials are
|
377
523
|
# provided to connect to Jenkins
|
378
|
-
# @raise [Exceptions::
|
379
|
-
#
|
380
|
-
# @raise [Exceptions::
|
381
|
-
#
|
524
|
+
# @raise [Exceptions::NotFound] When the requested page on Jenkins is not
|
525
|
+
# found
|
526
|
+
# @raise [Exceptions::InternalServerError] When Jenkins returns a 500
|
527
|
+
# Internal Server Error
|
382
528
|
# @raise [Exceptions::ApiException] Any other exception returned from
|
383
529
|
# Jenkins that are not categorized in the API Client.
|
384
530
|
#
|
385
531
|
def handle_exception(response, to_send = "code", send_json = false)
|
386
532
|
msg = "HTTP Code: #{response.code}, Response Body: #{response.body}"
|
387
|
-
|
533
|
+
@logger.debug msg
|
388
534
|
case response.code.to_i
|
389
535
|
# As of Jenkins version 1.519, the job builds return a 201 status code
|
390
536
|
# with a Location HTTP header with the pointing the URL of the item in
|
@@ -396,24 +542,40 @@ module JenkinsApi
|
|
396
542
|
return response.body
|
397
543
|
elsif to_send == "code"
|
398
544
|
return response.code
|
545
|
+
elsif to_send == "raw"
|
546
|
+
return response
|
399
547
|
end
|
400
548
|
when 400
|
401
|
-
|
549
|
+
matched = response.body.match(/<p>(.*)<\/p>/)
|
550
|
+
api_message = matched[1] unless matched.nil?
|
551
|
+
@logger.debug "API message: #{api_message}"
|
552
|
+
case api_message
|
402
553
|
when /A job already exists with the name/
|
403
|
-
raise Exceptions::
|
554
|
+
raise Exceptions::JobAlreadyExists.new(@logger, api_message)
|
555
|
+
when /A view already exists with the name/
|
556
|
+
raise Exceptions::ViewAlreadyExists.new(@logger, api_message)
|
557
|
+
when /Slave called .* already exists/
|
558
|
+
raise Exceptions::NodeAlreadyExists.new(@logger, api_message)
|
404
559
|
when /Nothing is submitted/
|
405
|
-
raise Exceptions::NothingSubmitted.new
|
560
|
+
raise Exceptions::NothingSubmitted.new(@logger, api_message)
|
406
561
|
else
|
407
|
-
raise Exceptions::ApiException.new(
|
562
|
+
raise Exceptions::ApiException.new(@logger, api_message)
|
408
563
|
end
|
409
564
|
when 401
|
410
|
-
raise Exceptions::
|
565
|
+
raise Exceptions::Unauthorized.new @logger
|
566
|
+
when 403
|
567
|
+
raise Exceptions::Forbidden.new @logger
|
411
568
|
when 404
|
412
|
-
raise Exceptions::
|
569
|
+
raise Exceptions::NotFound.new @logger
|
413
570
|
when 500
|
414
|
-
raise Exceptions::
|
571
|
+
raise Exceptions::InternalServerError.new @logger
|
572
|
+
when 503
|
573
|
+
raise Exceptions::ServiceUnavailable.new @logger
|
415
574
|
else
|
416
|
-
raise Exceptions::ApiException.new(
|
575
|
+
raise Exceptions::ApiException.new(
|
576
|
+
@logger,
|
577
|
+
"Error code #{response.code}"
|
578
|
+
)
|
417
579
|
end
|
418
580
|
end
|
419
581
|
|