jenkins_api_client 0.12.1 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|