jenkins_api_client2 1.9.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.
- checksums.yaml +7 -0
- data/bin/jenkinscli2 +5 -0
- data/java_deps/jenkins-cli.jar +0 -0
- data/jenkins_api_client2.gemspec +30 -0
- data/lib/jenkins_api_client2/build_queue.rb +262 -0
- data/lib/jenkins_api_client2/cli/base.rb +84 -0
- data/lib/jenkins_api_client2/cli/helper.rb +61 -0
- data/lib/jenkins_api_client2/cli/job.rb +133 -0
- data/lib/jenkins_api_client2/cli/node.rb +97 -0
- data/lib/jenkins_api_client2/cli/system.rb +65 -0
- data/lib/jenkins_api_client2/client.rb +855 -0
- data/lib/jenkins_api_client2/exceptions.rb +246 -0
- data/lib/jenkins_api_client2/job.rb +1966 -0
- data/lib/jenkins_api_client2/node.rb +353 -0
- data/lib/jenkins_api_client2/plugin_manager.rb +460 -0
- data/lib/jenkins_api_client2/plugin_settings/base.rb +11 -0
- data/lib/jenkins_api_client2/plugin_settings/collection.rb +39 -0
- data/lib/jenkins_api_client2/plugin_settings/hipchat.rb +53 -0
- data/lib/jenkins_api_client2/plugin_settings/workspace_cleanup.rb +35 -0
- data/lib/jenkins_api_client2/root.rb +67 -0
- data/lib/jenkins_api_client2/system.rb +134 -0
- data/lib/jenkins_api_client2/urihelper.rb +18 -0
- data/lib/jenkins_api_client2/user.rb +131 -0
- data/lib/jenkins_api_client2/version.rb +36 -0
- data/lib/jenkins_api_client2/view.rb +313 -0
- data/lib/jenkins_api_client2.rb +52 -0
- metadata +170 -0
@@ -0,0 +1,1966 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2012-2013 Kannan Manickam <arangamani.kannan@gmail.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
|
23
|
+
require 'jenkins_api_client2/urihelper'
|
24
|
+
|
25
|
+
module JenkinsApi
|
26
|
+
class Client
|
27
|
+
# This class communicates with the Jenkins "/job" API to obtain details
|
28
|
+
# about jobs, creating, deleting, building, and various other operations.
|
29
|
+
#
|
30
|
+
class Job
|
31
|
+
include JenkinsApi::UriHelper
|
32
|
+
|
33
|
+
# Version that jenkins started to include queued build info in build response
|
34
|
+
JENKINS_QUEUE_ID_SUPPORT_VERSION = '1.519'
|
35
|
+
|
36
|
+
attr_reader :plugin_collection
|
37
|
+
|
38
|
+
# Initialize the Job object and store the reference to Client object
|
39
|
+
#
|
40
|
+
# @param client [Client] the client object
|
41
|
+
#
|
42
|
+
# @return [Job] the job object
|
43
|
+
#
|
44
|
+
def initialize(client, *plugin_settings)
|
45
|
+
@client = client
|
46
|
+
@logger = @client.logger
|
47
|
+
@plugin_collection = JenkinsApi::Client::PluginSettings::Collection.new(*plugin_settings)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add a plugin to be included in job's xml configureation
|
51
|
+
#
|
52
|
+
# @param plugin [Jenkins::Api::Client::PluginSettings::Base]
|
53
|
+
#
|
54
|
+
# @return [JenkinsApi::Client::PluginSettings::Collection] the job object
|
55
|
+
def add_plugin(plugin)
|
56
|
+
plugin_collection.add(plugin)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Remove a plugin to be included in job's xml configureation
|
60
|
+
#
|
61
|
+
# @param plugin [Jenkins::Api::Client::PluginSettings::Base]
|
62
|
+
#
|
63
|
+
# @return [JenkinsApi::Client::PluginSettings::Collection] the job object
|
64
|
+
def remove_plugin(plugin)
|
65
|
+
plugin_collection.remove(plugin)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return a string representation of the object
|
69
|
+
#
|
70
|
+
def to_s
|
71
|
+
"#<JenkinsApi::Client::Job>"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create or Update a job with the name specified and the xml given
|
75
|
+
#
|
76
|
+
# @param job_name [String] the name of the job
|
77
|
+
# @param xml [String] the xml configuration of the job
|
78
|
+
#
|
79
|
+
# @see #create
|
80
|
+
# @see #update
|
81
|
+
#
|
82
|
+
# @return [String] the HTTP status code from the POST request
|
83
|
+
#
|
84
|
+
def create_or_update(job_name, xml)
|
85
|
+
if exists?(job_name)
|
86
|
+
update(job_name, xml)
|
87
|
+
else
|
88
|
+
create(job_name, xml)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Create a job with the name specified and the xml given
|
93
|
+
#
|
94
|
+
# @param job_name [String] the name of the job
|
95
|
+
# @param xml [String] the xml configuration of the job
|
96
|
+
#
|
97
|
+
# @see #create_or_update
|
98
|
+
# @see #update
|
99
|
+
#
|
100
|
+
# @return [String] the HTTP status code from the POST request
|
101
|
+
#
|
102
|
+
def create(job_name, xml)
|
103
|
+
@logger.info "Creating job '#{job_name}'"
|
104
|
+
@client.post_config("/createItem?name=#{form_encode job_name}", xml)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Update a job with the name specified and the xml given
|
108
|
+
#
|
109
|
+
# @param job_name [String] the name of the job
|
110
|
+
# @param xml [String] the xml configuration of the job
|
111
|
+
#
|
112
|
+
# @see #create_or_update
|
113
|
+
# @see #create
|
114
|
+
#
|
115
|
+
# @return [String] the HTTP status code from the POST request
|
116
|
+
#
|
117
|
+
def update(job_name, xml)
|
118
|
+
@logger.info "Updating job '#{job_name}'"
|
119
|
+
post_config(job_name, xml)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Create or Update a job with params given as a hash instead of the xml
|
123
|
+
# This gives some flexibility for creating/updating simple jobs so the
|
124
|
+
# user doesn't have to learn about handling xml.
|
125
|
+
#
|
126
|
+
# @param params [Hash] parameters to create a freestyle project
|
127
|
+
#
|
128
|
+
# @option params [String] :name
|
129
|
+
# the name of the job
|
130
|
+
# @option params [Boolean] :keep_dependencies (false)
|
131
|
+
# whether to keep the dependencies or not
|
132
|
+
# @option params [Boolean] :block_build_when_downstream_building (false)
|
133
|
+
# whether to block build when the downstream project is building
|
134
|
+
# @option params [Boolean] :block_build_when_upstream_building (false)
|
135
|
+
# whether to block build when the upstream project is building
|
136
|
+
# @option params [Boolean] :concurrent_build (false)
|
137
|
+
# whether to allow concurrent execution of builds
|
138
|
+
# @option params [String] :scm_provider
|
139
|
+
# the type of source control. Supported providers: git, svn, and cvs
|
140
|
+
# @option params [String] :scm_url
|
141
|
+
# the remote url for the selected scm provider
|
142
|
+
# @option params [String] :scm_credentials_id
|
143
|
+
# the id of the credentials to use for authenticating with scm. Only for "git"
|
144
|
+
# @option params [String] :scm_git_tool
|
145
|
+
# the git executable. Defaults to "Default"; only for "git"
|
146
|
+
# @option params [String] :scm_module
|
147
|
+
# the module to download. Only for use with "cvs" scm provider
|
148
|
+
# @option params [String] :scm_branch (master)
|
149
|
+
# the branch to use in scm.
|
150
|
+
# @option params [String] :scm_tag
|
151
|
+
# the tag to download from scm. Only for use with "cvs" scm provider
|
152
|
+
# @option params [Boolean] :scm_use_head_if_tag_not_found
|
153
|
+
# whether to use head if specified tag is not found. Only for "cvs"
|
154
|
+
# @option params [String] :timer
|
155
|
+
# the timer for running builds periodically
|
156
|
+
# @option params [String] :shell_command
|
157
|
+
# the command to execute in the shell
|
158
|
+
# @option params [String] :notification_email
|
159
|
+
# the email for sending notification
|
160
|
+
# @option params [String] :skype_targets
|
161
|
+
# the skype targets for sending notifications to. Use * to specify
|
162
|
+
# group chats. Use space to separate multiple targets. Note that this
|
163
|
+
# option requires the "skype" plugin to be installed in jenkins.
|
164
|
+
# Example: testuser *testgroup
|
165
|
+
# @option params [String] :skype_strategy (change)
|
166
|
+
# the skype strategy to be used for sending notifications.
|
167
|
+
# Valid values: all, failure, failure_and_fixed, change.
|
168
|
+
# @option params [Boolean] :skype_notify_on_build_start (false)
|
169
|
+
# whether to notify skype targets on build start
|
170
|
+
# @option params [Boolean] :skype_notify_suspects (false)
|
171
|
+
# whether to notify suspects on skype
|
172
|
+
# @option params [Boolean] :skype_notify_culprits (false)
|
173
|
+
# whether to notify culprits on skype
|
174
|
+
# @option params [Boolean] :skype_notify_fixers (false)
|
175
|
+
# whether to notify fixers on skype
|
176
|
+
# @option params [Boolean] :skype_notify_upstream_committers (false)
|
177
|
+
# whether to notify upstream committers on skype
|
178
|
+
# @option params [String] :skype_message (summary_and_scm_changes)
|
179
|
+
# the information to be sent as notification message. Valid:
|
180
|
+
# just_summary, summary_and_scm_changes,
|
181
|
+
# summary_and_build_parameters, summary_scm_changes_and_failed_tests.
|
182
|
+
# @option params [String] :child_projects
|
183
|
+
# the projects to add as downstream projects
|
184
|
+
# @option params [String] :child_threshold (failure)
|
185
|
+
# the threshold for child projects. Valid options: success, failure,
|
186
|
+
# or unstable.
|
187
|
+
#
|
188
|
+
# @see #create_freestyle
|
189
|
+
# @see #update_freestyle
|
190
|
+
#
|
191
|
+
# @return [String] the HTTP status code from the POST request
|
192
|
+
#
|
193
|
+
def create_or_update_freestyle(params)
|
194
|
+
if exists?(params[:name])
|
195
|
+
update_freestyle(params)
|
196
|
+
else
|
197
|
+
create_freestyle(params)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Create a freestyle project by accepting a Hash of parameters. For the
|
202
|
+
# parameter description see #create_of_update_freestyle
|
203
|
+
#
|
204
|
+
# @param params [Hash] the parameters for creating a job
|
205
|
+
#
|
206
|
+
# @example Create a Freestype Project
|
207
|
+
# create_freestyle(
|
208
|
+
# :name => "test_freestyle_job",
|
209
|
+
# :keep_dependencies => true,
|
210
|
+
# :concurrent_build => true,
|
211
|
+
# :scm_provider => "git",
|
212
|
+
# :scm_url => "git://github.com./asghaier/jenkins_api_client2.git",
|
213
|
+
# :scm_branch => "master",
|
214
|
+
# :shell_command => "bundle install\n rake func_tests"
|
215
|
+
# )
|
216
|
+
#
|
217
|
+
# @see #create_or_update_freestyle
|
218
|
+
# @see #create
|
219
|
+
# @see #update_freestyle
|
220
|
+
#
|
221
|
+
# @return [String] the HTTP status code from the POST request
|
222
|
+
#
|
223
|
+
def create_freestyle(params)
|
224
|
+
xml = build_freestyle_config(params)
|
225
|
+
create(params[:name], xml)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Update a job with params given as a hash instead of the xml. For the
|
229
|
+
# parameter description see #create_or_update_freestyle
|
230
|
+
#
|
231
|
+
# @param params [Hash] parameters to update a freestyle project
|
232
|
+
#
|
233
|
+
# @see #create_or_update_freestyle
|
234
|
+
# @see #update
|
235
|
+
# @see #create_freestyle
|
236
|
+
#
|
237
|
+
# @return [String] the HTTP status code from the POST request
|
238
|
+
#
|
239
|
+
def update_freestyle(params)
|
240
|
+
xml = build_freestyle_config(params)
|
241
|
+
update(params[:name], xml)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Builds the XML configuration based on the parameters passed as a Hash
|
245
|
+
#
|
246
|
+
# @param params [Hash] the parameters for building XML configuration
|
247
|
+
#
|
248
|
+
# @return [String] the generated XML configuration of the project
|
249
|
+
#
|
250
|
+
def build_freestyle_config(params)
|
251
|
+
# Supported SCM providers
|
252
|
+
supported_scm = ["git", "subversion", "cvs"]
|
253
|
+
|
254
|
+
# Set default values for params that are not specified.
|
255
|
+
raise ArgumentError, "Job name must be specified" \
|
256
|
+
unless params.is_a?(Hash) && params[:name]
|
257
|
+
|
258
|
+
[
|
259
|
+
:keep_dependencies,
|
260
|
+
:block_build_when_downstream_building,
|
261
|
+
:block_build_when_upstream_building,
|
262
|
+
:concurrent_build
|
263
|
+
].each do |param|
|
264
|
+
params[param] = false if params[param].nil?
|
265
|
+
end
|
266
|
+
|
267
|
+
if params[:notification_email]
|
268
|
+
if params[:notification_email_for_every_unstable].nil?
|
269
|
+
params[:notification_email_for_every_unstable] = false
|
270
|
+
end
|
271
|
+
if params[:notification_email_send_to_individuals].nil?
|
272
|
+
params[:notification_email_send_to_individuals] ||= false
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# SCM configurations and Error handling.
|
277
|
+
unless params[:scm_provider].nil?
|
278
|
+
unless supported_scm.include?(params[:scm_provider])
|
279
|
+
raise "SCM #{params[:scm_provider]} is currently not supported"
|
280
|
+
end
|
281
|
+
raise "SCM URL must be specified" if params[:scm_url].nil?
|
282
|
+
params[:scm_branch] = "master" if params[:scm_branch].nil?
|
283
|
+
if params[:scm_use_head_if_tag_not_found].nil?
|
284
|
+
params[:scm_use_head_if_tag_not_found] = false
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Child projects configuration and Error handling
|
289
|
+
if params[:child_threshold].nil? && !params[:child_projects].nil?
|
290
|
+
params[:child_threshold] = "failure"
|
291
|
+
end
|
292
|
+
|
293
|
+
@logger.debug "Creating a freestyle job with params: #{params.inspect}"
|
294
|
+
|
295
|
+
# Build the Job xml file based on the parameters given
|
296
|
+
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
|
297
|
+
xml.project do
|
298
|
+
xml.actions
|
299
|
+
xml.description
|
300
|
+
xml.keepDependencies "#{params[:keep_dependencies]}"
|
301
|
+
xml.properties
|
302
|
+
#buildlogs related stuff
|
303
|
+
if params[:discard_old_builds]
|
304
|
+
xml.logRotator(:class => 'hudson.tasks.LogRotator') do
|
305
|
+
xml.daysToKeep params[:discard_old_builds][:daysToKeep] || -1
|
306
|
+
xml.numToKeep params[:discard_old_builds][:numToKeep] || -1
|
307
|
+
xml.artifactDaysToKeep params[:discard_old_builds][:artifactDaysToKeep] || -1
|
308
|
+
xml.artifactNumToKeep params[:discard_old_builds][:artifactNumToKeep] || -1
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# SCM related stuff
|
313
|
+
if params[:scm_provider] == 'subversion'
|
314
|
+
# Build subversion related XML portion
|
315
|
+
scm_subversion(params, xml)
|
316
|
+
elsif params[:scm_provider] == "cvs"
|
317
|
+
# Build CVS related XML portion
|
318
|
+
scm_cvs(params, xml)
|
319
|
+
elsif params[:scm_provider] == "git"
|
320
|
+
# Build Git related XML portion
|
321
|
+
scm_git(params, xml)
|
322
|
+
else
|
323
|
+
xml.scm(:class => "hudson.scm.NullSCM")
|
324
|
+
end
|
325
|
+
# Restrict job to run in a specified node
|
326
|
+
if params[:restricted_node]
|
327
|
+
xml.assignedNode "#{params[:restricted_node]}"
|
328
|
+
xml.canRoam "false"
|
329
|
+
else
|
330
|
+
xml.canRoam "true"
|
331
|
+
end
|
332
|
+
xml.disabled "false"
|
333
|
+
xml.blockBuildWhenDownstreamBuilding(
|
334
|
+
"#{params[:block_build_when_downstream_building]}")
|
335
|
+
xml.blockBuildWhenUpstreamBuilding(
|
336
|
+
"#{params[:block_build_when_upstream_building]}")
|
337
|
+
xml.triggers.vector do
|
338
|
+
if params[:timer]
|
339
|
+
xml.send("hudson.triggers.TimerTrigger") do
|
340
|
+
xml.spec params[:timer]
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
if params[:scm_trigger]
|
345
|
+
xml.send("hudson.triggers.SCMTrigger") do
|
346
|
+
xml.spec params[:scm_trigger]
|
347
|
+
xml.ignorePostCommitHooks params.fetch(:ignore_post_commit_hooks) { false }
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
xml.concurrentBuild "#{params[:concurrent_build]}"
|
352
|
+
# Shell command stuff
|
353
|
+
xml.builders do
|
354
|
+
if params[:shell_command]
|
355
|
+
xml.send("hudson.tasks.Shell") do
|
356
|
+
xml.command "#{params[:shell_command]}"
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
# Adding Downstream projects
|
361
|
+
xml.publishers do
|
362
|
+
# Build portion of XML that adds child projects
|
363
|
+
child_projects(params, xml) if params[:child_projects]
|
364
|
+
# Build portion of XML that adds email notification
|
365
|
+
notification_email(params, xml) if params[:notification_email]
|
366
|
+
# Build portion of XML that adds skype notification
|
367
|
+
skype_notification(params, xml) if params[:skype_targets]
|
368
|
+
artifact_archiver(params[:artifact_archiver], xml)
|
369
|
+
end
|
370
|
+
xml.buildWrappers
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
xml_doc = Nokogiri::XML(builder.to_xml)
|
375
|
+
plugin_collection.configure(xml_doc).to_xml
|
376
|
+
end
|
377
|
+
|
378
|
+
|
379
|
+
# Adding email notification to a job
|
380
|
+
#
|
381
|
+
# @param [Hash] params parameters to add email notification
|
382
|
+
#
|
383
|
+
# @option params [String] :name Name of the job
|
384
|
+
# @option params [String] :notification_email Email address to send
|
385
|
+
# @option params [Boolean] :notification_email_for_every_unstable
|
386
|
+
# Send email notification email for every unstable build
|
387
|
+
#
|
388
|
+
def add_email_notification(params)
|
389
|
+
raise "No job name specified" unless params[:name]
|
390
|
+
raise "No email address specified" unless params[:notification_email]
|
391
|
+
@logger.info "Adding '#{params[:notification_email]}' to be" +
|
392
|
+
" notified for '#{params[:name]}'"
|
393
|
+
xml = get_config(params[:name])
|
394
|
+
n_xml = Nokogiri::XML(xml)
|
395
|
+
if n_xml.xpath("//hudson.tasks.Mailer").empty?
|
396
|
+
p_xml = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |b_xml|
|
397
|
+
notification_email(params, b_xml)
|
398
|
+
end
|
399
|
+
email_xml = Nokogiri::XML(p_xml.to_xml).xpath(
|
400
|
+
"//hudson.tasks.Mailer"
|
401
|
+
).first
|
402
|
+
n_xml.xpath("//publishers").first.add_child(email_xml)
|
403
|
+
post_config(params[:name], n_xml.to_xml)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
# Adding skype notificaiton to a job
|
408
|
+
#
|
409
|
+
# @param [Hash] params parameters for adding skype notification
|
410
|
+
# * +:name+ name of the job to add skype notification
|
411
|
+
# * +:skype_targets+ skype targets for sending notifications to. Use *
|
412
|
+
# to specify group chats. Use space to separate multiple targets.
|
413
|
+
# Example: testuser, *testgroup.
|
414
|
+
# * +:skype_strategy+ skype strategy to be used for sending
|
415
|
+
# notifications. Valid values: all, failure, failure_and_fixed,
|
416
|
+
# change. Default: change.
|
417
|
+
# * +:skype_notify_on_build_start+ Default: false
|
418
|
+
# * +:skype_notify_suspects+ Default: false
|
419
|
+
# * +:skype_notify_culprits+ Default: false
|
420
|
+
# * +:skype_notify_fixers+ Default: false
|
421
|
+
# * +:skype_notify_upstream_committers+ Default: false
|
422
|
+
# * +:skype_message+ what should be sent as notification message. Valid:
|
423
|
+
# just_summary, summary_and_scm_changes, summary_and_build_parameters,
|
424
|
+
# summary_scm_changes_and_failed_tests.
|
425
|
+
# Default: summary_and_scm_changes
|
426
|
+
#
|
427
|
+
def add_skype_notification(params)
|
428
|
+
raise "No job name specified" unless params[:name]
|
429
|
+
raise "No Skype target specified" unless params[:skype_targets]
|
430
|
+
@logger.info "Adding Skype notification for '#{params[:name]}'"
|
431
|
+
xml = get_config(params[:name])
|
432
|
+
n_xml = Nokogiri::XML(xml)
|
433
|
+
if n_xml.xpath("//hudson.plugins.skype.im.transport.SkypePublisher").empty?
|
434
|
+
p_xml = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |b_xml|
|
435
|
+
skype_notification(params, b_xml)
|
436
|
+
end
|
437
|
+
skype_xml = Nokogiri::XML(p_xml.to_xml).xpath(
|
438
|
+
"//hudson.plugins.skype.im.transport.SkypePublisher"
|
439
|
+
).first
|
440
|
+
n_xml.xpath("//publishers").first.add_child(skype_xml)
|
441
|
+
post_config(params[:name], n_xml.to_xml)
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
# Configure post-build step to archive artifacts
|
446
|
+
#
|
447
|
+
# @param artifact_params [Hash] parameters controlling how artifacts are archived
|
448
|
+
#
|
449
|
+
# @option artifact_params [String] :artifact_files
|
450
|
+
# pattern or names of files to archive
|
451
|
+
# @option artifact_params [String] :excludes
|
452
|
+
# pattern or names of files to exclude
|
453
|
+
# @option artifact_params [Boolean] :fingerprint (false)
|
454
|
+
# fingerprint the archives
|
455
|
+
# @option artifact_params [Boolean] :allow_empty_archive (false)
|
456
|
+
# whether to allow empty archives
|
457
|
+
# @option artifact_params [Boolean] :only_if_successful (false)
|
458
|
+
# only archive if successful
|
459
|
+
# @option artifact_params [Boolean] :default_excludes (false)
|
460
|
+
# exclude defaults automatically
|
461
|
+
#
|
462
|
+
# @return [Nokogiri::XML::Builder]
|
463
|
+
#
|
464
|
+
def artifact_archiver(artifact_params, xml)
|
465
|
+
return xml if artifact_params.nil?
|
466
|
+
|
467
|
+
xml.send('hudson.tasks.ArtifactArchiver') do |x|
|
468
|
+
x.artifacts artifact_params.fetch(:artifact_files) { '' }
|
469
|
+
x.excludes artifact_params.fetch(:excludes) { '' }
|
470
|
+
x.fingerprint artifact_params.fetch(:fingerprint) { false }
|
471
|
+
x.allowEmptyArchive artifact_params.fetch(:allow_empty_archive) { false }
|
472
|
+
x.onlyIfSuccessful artifact_params.fetch(:only_if_successful) { false }
|
473
|
+
x.defaultExcludes artifact_params.fetch(:default_excludes) { false }
|
474
|
+
end
|
475
|
+
|
476
|
+
xml
|
477
|
+
end
|
478
|
+
|
479
|
+
# Rename a job given the old name and new name
|
480
|
+
#
|
481
|
+
# @param [String] old_job Name of the old job
|
482
|
+
# @param [String] new_job Name of the new job.
|
483
|
+
#
|
484
|
+
def rename(old_job, new_job)
|
485
|
+
@logger.info "Renaming job '#{old_job}' to '#{new_job}'"
|
486
|
+
@client.api_post_request("/job/#{path_encode old_job}/doRename?newName=#{form_encode new_job}")
|
487
|
+
end
|
488
|
+
|
489
|
+
# Delete a job given the name
|
490
|
+
#
|
491
|
+
# @param job_name [String] the name of the job to delete
|
492
|
+
#
|
493
|
+
# @return [String] the response from the HTTP POST request
|
494
|
+
#
|
495
|
+
def delete(job_name)
|
496
|
+
@logger.info "Deleting job '#{job_name}'"
|
497
|
+
@client.api_post_request("/job/#{path_encode job_name}/doDelete")
|
498
|
+
end
|
499
|
+
|
500
|
+
# Deletes all jobs from Jenkins
|
501
|
+
#
|
502
|
+
# @note This method will remove all jobs from Jenkins. Please use with
|
503
|
+
# caution.
|
504
|
+
#
|
505
|
+
def delete_all!
|
506
|
+
@logger.info "Deleting all jobs from jenkins"
|
507
|
+
list_all.each { |job| delete(job) }
|
508
|
+
end
|
509
|
+
|
510
|
+
# Wipe out the workspace for a job given the name
|
511
|
+
#
|
512
|
+
# @param job_name [String] the name of the job to wipe out the workspace
|
513
|
+
#
|
514
|
+
# @return [String] response from the HTTP POST request
|
515
|
+
#
|
516
|
+
def wipe_out_workspace(job_name)
|
517
|
+
@logger.info "Wiping out the workspace of job '#{job_name}'"
|
518
|
+
@client.api_post_request("/job/#{path_encode job_name}/doWipeOutWorkspace")
|
519
|
+
end
|
520
|
+
|
521
|
+
# Stops a running build of a job
|
522
|
+
# This method will stop the current/most recent build if no build number
|
523
|
+
# is specified. The build will be stopped only if it was
|
524
|
+
# in 'running' state.
|
525
|
+
#
|
526
|
+
# @param job_name [String] the name of the job to stop the build
|
527
|
+
# @param build_number [Number] the build number to stop
|
528
|
+
#
|
529
|
+
def stop_build(job_name, build_number = 0)
|
530
|
+
build_number = get_current_build_number(job_name) if build_number == 0
|
531
|
+
raise "No builds for #{job_name}" unless build_number
|
532
|
+
@logger.info "Stopping job '#{job_name}' Build ##{build_number}"
|
533
|
+
# Check and see if the build is running
|
534
|
+
is_building = @client.api_get_request(
|
535
|
+
"/job/#{path_encode job_name}/#{build_number}"
|
536
|
+
)["building"]
|
537
|
+
if is_building
|
538
|
+
@client.api_post_request("/job/#{path_encode job_name}/#{build_number}/stop")
|
539
|
+
end
|
540
|
+
end
|
541
|
+
alias_method :stop, :stop_build
|
542
|
+
alias_method :abort, :stop_build
|
543
|
+
|
544
|
+
# Re-create the same job
|
545
|
+
# This is a hack to clear any existing builds
|
546
|
+
#
|
547
|
+
# @param job_name [String] the name of the job to recreate
|
548
|
+
#
|
549
|
+
# @return [String] the response from the HTTP POST request
|
550
|
+
#
|
551
|
+
def recreate(job_name)
|
552
|
+
@logger.info "Recreating job '#{job_name}'"
|
553
|
+
job_xml = get_config(job_name)
|
554
|
+
delete(job_name)
|
555
|
+
create(job_name, job_xml)
|
556
|
+
end
|
557
|
+
|
558
|
+
# Copy a job
|
559
|
+
#
|
560
|
+
# @param from_job_name [String] the name of the job to copy from
|
561
|
+
# @param to_job_name [String] the name of the job to copy to
|
562
|
+
#
|
563
|
+
# @return [String] the response from the HTTP POST request
|
564
|
+
#
|
565
|
+
def copy(from_job_name, to_job_name=nil)
|
566
|
+
to_job_name = "copy_of_#{from_job_name}" if to_job_name.nil?
|
567
|
+
@logger.info "Copying job '#{from_job_name}' to '#{to_job_name}'"
|
568
|
+
@client.api_post_request(
|
569
|
+
"/createItem?name=#{path_encode to_job_name}&mode=copy&from=#{path_encode from_job_name}"
|
570
|
+
)
|
571
|
+
end
|
572
|
+
|
573
|
+
# Get progressive console output from Jenkins server for a job
|
574
|
+
#
|
575
|
+
# @param [String] job_name Name of the Jenkins job
|
576
|
+
# @param [Number] build_num Specific build number to obtain the
|
577
|
+
# console output from. Default is the recent build
|
578
|
+
# @param [Number] start start offset to get only a portion of the text
|
579
|
+
# @param [String] mode Mode of text output. 'text' or 'html'
|
580
|
+
#
|
581
|
+
# @return [Hash] response
|
582
|
+
# * +output+ console output of the job
|
583
|
+
# * +size+ size of the text. This can be used as 'start' for the
|
584
|
+
# next call to get progressive output
|
585
|
+
# * +more+ more data available for the job. 'true' if available
|
586
|
+
# and nil otherwise
|
587
|
+
#
|
588
|
+
def get_console_output(job_name, build_num = 0, start = 0, mode = 'text')
|
589
|
+
build_num = get_current_build_number(job_name) if build_num == 0
|
590
|
+
if build_num == 0
|
591
|
+
puts "No builds for this job '#{job_name}' yet."
|
592
|
+
return nil
|
593
|
+
end
|
594
|
+
if mode == 'text'
|
595
|
+
mode = 'Text'
|
596
|
+
elsif mode == 'html'
|
597
|
+
mode = 'Html'
|
598
|
+
else
|
599
|
+
raise "Mode should either be 'text' or 'html'. You gave: #{mode}"
|
600
|
+
end
|
601
|
+
get_msg = "/job/#{path_encode job_name}/#{build_num}/logText/progressive#{mode}?"
|
602
|
+
get_msg << "start=#{start}"
|
603
|
+
raw_response = true
|
604
|
+
api_response = @client.api_get_request(get_msg, nil, nil, raw_response)
|
605
|
+
#puts "Response: #{api_response.header['x-more-data']}"
|
606
|
+
response = {}
|
607
|
+
response['output'] = api_response.body
|
608
|
+
response['size'] = api_response.header['x-text-size']
|
609
|
+
response['more'] = api_response.header['x-more-data']
|
610
|
+
|
611
|
+
response
|
612
|
+
end
|
613
|
+
|
614
|
+
# List all jobs on the Jenkins CI server
|
615
|
+
#
|
616
|
+
# @return [Array<String>] the names of all jobs in jenkins
|
617
|
+
#
|
618
|
+
def list_all
|
619
|
+
response_json = @client.api_get_request("", "tree=jobs[name]")["jobs"]
|
620
|
+
response_json.map { |job| job["name"] }.sort
|
621
|
+
end
|
622
|
+
|
623
|
+
# Checks if the given job exists in Jenkins
|
624
|
+
#
|
625
|
+
# @param job_name [String] the name of the job to check
|
626
|
+
#
|
627
|
+
# @return [Boolean] whether the job exists in jenkins or not
|
628
|
+
#
|
629
|
+
def exists?(job_name)
|
630
|
+
list(job_name).include?(job_name)
|
631
|
+
end
|
632
|
+
|
633
|
+
# List all Jobs matching the given status
|
634
|
+
# You can optionally pass in jobs list to filter the status from
|
635
|
+
#
|
636
|
+
# @param status [String] the job status to filter
|
637
|
+
# @param jobs [Array<String>] if specified this array will be used for
|
638
|
+
# filtering by the status otherwise the filtering will be done using
|
639
|
+
# all jobs available in jenkins
|
640
|
+
#
|
641
|
+
# @return [Array<String>] filtered jobs
|
642
|
+
#
|
643
|
+
def list_by_status(status, jobs = [])
|
644
|
+
jobs = list_all if jobs.empty?
|
645
|
+
@logger.info "Obtaining jobs matching status '#{status}'"
|
646
|
+
json_response = @client.api_get_request("", "tree=jobs[name,color]")
|
647
|
+
filtered_jobs = []
|
648
|
+
json_response["jobs"].each do |job|
|
649
|
+
if color_to_status(job["color"]) == status &&
|
650
|
+
jobs.include?(job["name"])
|
651
|
+
filtered_jobs << job["name"]
|
652
|
+
end
|
653
|
+
end
|
654
|
+
filtered_jobs
|
655
|
+
end
|
656
|
+
|
657
|
+
# List all jobs that match the given regex
|
658
|
+
#
|
659
|
+
# @param filter [String] a regular expression or a string to filter jobs
|
660
|
+
# @param ignorecase [Boolean] whether to ignore case or not
|
661
|
+
#
|
662
|
+
# @return [Array<String>] jobs matching the given pattern
|
663
|
+
#
|
664
|
+
def list(filter, ignorecase = true)
|
665
|
+
@logger.info "Obtaining jobs matching filter '#{filter}'"
|
666
|
+
response_json = @client.api_get_request("")
|
667
|
+
jobs = []
|
668
|
+
response_json["jobs"].each do |job|
|
669
|
+
if ignorecase
|
670
|
+
jobs << job["name"] if job["name"] =~ /#{filter}/i
|
671
|
+
else
|
672
|
+
jobs << job["name"] if job["name"] =~ /#{filter}/
|
673
|
+
end
|
674
|
+
end
|
675
|
+
jobs
|
676
|
+
end
|
677
|
+
|
678
|
+
# List all jobs on the Jenkins CI server along with their details
|
679
|
+
#
|
680
|
+
# @return [Array<Hash>] the details of all jobs in jenkins
|
681
|
+
#
|
682
|
+
def list_all_with_details
|
683
|
+
@logger.info "Obtaining the details of all jobs"
|
684
|
+
response_json = @client.api_get_request("")
|
685
|
+
response_json["jobs"]
|
686
|
+
end
|
687
|
+
|
688
|
+
# List details of a specific job
|
689
|
+
#
|
690
|
+
# @param job_name [String] the name of the job to obtain the details from
|
691
|
+
#
|
692
|
+
# @return [Hash] the details of the specified job
|
693
|
+
#
|
694
|
+
def list_details(job_name)
|
695
|
+
@logger.info "Obtaining the details of '#{job_name}'"
|
696
|
+
@client.api_get_request("/job/#{path_encode job_name}")
|
697
|
+
end
|
698
|
+
|
699
|
+
# List upstream projects of a specific job
|
700
|
+
#
|
701
|
+
# @param job_name [String] the name of the job to obtain upstream
|
702
|
+
# projects for
|
703
|
+
#
|
704
|
+
def get_upstream_projects(job_name)
|
705
|
+
@logger.info "Obtaining the upstream projects of '#{job_name}'"
|
706
|
+
response_json = @client.api_get_request("/job/#{path_encode job_name}")
|
707
|
+
response_json["upstreamProjects"]
|
708
|
+
end
|
709
|
+
|
710
|
+
# List downstream projects of a specific job
|
711
|
+
#
|
712
|
+
# @param job_name [String] the name of the job to obtain downstream
|
713
|
+
# projects for
|
714
|
+
#
|
715
|
+
def get_downstream_projects(job_name)
|
716
|
+
@logger.info "Obtaining the down stream projects of '#{job_name}'"
|
717
|
+
response_json = @client.api_get_request("/job/#{path_encode job_name}")
|
718
|
+
response_json["downstreamProjects"]
|
719
|
+
end
|
720
|
+
|
721
|
+
# Obtain build details of a specific job
|
722
|
+
#
|
723
|
+
# @param [String] job_name
|
724
|
+
#
|
725
|
+
def get_builds(job_name, options = {})
|
726
|
+
@logger.info "Obtaining the build details of '#{job_name}'"
|
727
|
+
url = "/job/#{path_encode job_name}"
|
728
|
+
|
729
|
+
tree = options[:tree] || nil
|
730
|
+
response_json = @client.api_get_request url, tree_string(tree)
|
731
|
+
response_json["builds"]
|
732
|
+
end
|
733
|
+
|
734
|
+
# This method maps the color to status of a job
|
735
|
+
#
|
736
|
+
# @param [String] color color given by the API for a job
|
737
|
+
#
|
738
|
+
# @return [String] status status of the given job matching the color
|
739
|
+
#
|
740
|
+
def color_to_status(color)
|
741
|
+
case color
|
742
|
+
when "blue"
|
743
|
+
"success"
|
744
|
+
when "red"
|
745
|
+
"failure"
|
746
|
+
when "yellow"
|
747
|
+
"unstable"
|
748
|
+
when /anime/
|
749
|
+
"running"
|
750
|
+
# In the recent version of Jenkins (> 1.517), jobs that are not built
|
751
|
+
# yet have a color of "notbuilt" instead of "grey". Include that to the
|
752
|
+
# not_run condition so it is backward compatible.
|
753
|
+
when "grey", "notbuilt"
|
754
|
+
"not_run"
|
755
|
+
when "aborted"
|
756
|
+
"aborted"
|
757
|
+
when "disabled"
|
758
|
+
"disabled"
|
759
|
+
else
|
760
|
+
"invalid"
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
764
|
+
# Determine if the build is queued
|
765
|
+
#
|
766
|
+
# @param [String] job_name
|
767
|
+
#
|
768
|
+
# @return [Integer] build number if queued, or [Boolean] false if not queued
|
769
|
+
#
|
770
|
+
def queued?(job_name)
|
771
|
+
queue_result = @client.api_get_request("/job/#{path_encode job_name}")['inQueue']
|
772
|
+
if queue_result
|
773
|
+
return @client.api_get_request("/job/#{path_encode job_name}")['nextBuildNumber']
|
774
|
+
else
|
775
|
+
return queue_result
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
# Obtain the current build status of the job
|
780
|
+
# By default Jenkins returns the color of the job status icon
|
781
|
+
# This function translates the color into a meaningful status
|
782
|
+
#
|
783
|
+
# @param [String] job_name
|
784
|
+
#
|
785
|
+
# @return [String] status current status of the given job
|
786
|
+
#
|
787
|
+
def get_current_build_status(job_name)
|
788
|
+
@logger.info "Obtaining the current build status of '#{job_name}'"
|
789
|
+
response_json = @client.api_get_request("/job/#{path_encode job_name}")
|
790
|
+
color_to_status(response_json["color"])
|
791
|
+
end
|
792
|
+
alias_method :status, :get_current_build_status
|
793
|
+
|
794
|
+
# Obtain the current build number of the given job
|
795
|
+
# This function returns nil if there were no builds for the given job.
|
796
|
+
#
|
797
|
+
# @param [String] job_name
|
798
|
+
#
|
799
|
+
# @return [Integer] current build number of the given job
|
800
|
+
#
|
801
|
+
def get_current_build_number(job_name)
|
802
|
+
@logger.info "Obtaining the current build number of '#{job_name}'"
|
803
|
+
@client.api_get_request("/job/#{path_encode job_name}")['nextBuildNumber'].to_i - 1
|
804
|
+
end
|
805
|
+
alias_method :build_number, :get_current_build_number
|
806
|
+
|
807
|
+
# Build a Jenkins job, optionally waiting for build to start and
|
808
|
+
# returning the build number.
|
809
|
+
# Adds support for new/old Jenkins servers where build_queue id may
|
810
|
+
# not be available. Also adds support for periodic callbacks, and
|
811
|
+
# optional cancellation of queued_job if not started within allowable
|
812
|
+
# time window (if build_queue option available)
|
813
|
+
#
|
814
|
+
# Notes:
|
815
|
+
# 'opts' may be a 'true' or 'false' value to maintain
|
816
|
+
# compatibility with old method signature, where true indicates
|
817
|
+
# 'return_build_number'. In this case, true is translated to:
|
818
|
+
# { 'build_start_timeout' => @client_timeout }
|
819
|
+
# which simulates earlier behavior.
|
820
|
+
#
|
821
|
+
# progress_proc
|
822
|
+
# Optional proc that is called periodically while waiting for
|
823
|
+
# build to start.
|
824
|
+
# Initial call (with poll_count == 0) indicates build has been
|
825
|
+
# requested, and that polling is starting.
|
826
|
+
# Final call will indicate one of build_started or cancelled.
|
827
|
+
# params:
|
828
|
+
# max_wait [Integer] Same as opts['build_start_timeout']
|
829
|
+
# current_wait [Integer]
|
830
|
+
# poll_count [Integer] How many times has queue been polled
|
831
|
+
#
|
832
|
+
# completion_proc
|
833
|
+
# Optional proc that is called <just before> the 'build' method
|
834
|
+
# exits.
|
835
|
+
# params:
|
836
|
+
# build_number [Integer] Present if build started or nil
|
837
|
+
# build_cancelled [Boolean] True if build timed out and was
|
838
|
+
# successfully removed from build-queue
|
839
|
+
#
|
840
|
+
# @param [String] job_name the name of the job
|
841
|
+
# @param [Hash] params the parameters for parameterized build
|
842
|
+
# @param [Hash] opts options for this method
|
843
|
+
# * +build_start_timeout+ [Integer] How long to wait for queued
|
844
|
+
# build to start before giving up. Default: 0/nil
|
845
|
+
# * +cancel_on_build_start_timeout+ [Boolean] Should an attempt be
|
846
|
+
# made to cancel the queued build if it hasn't started within
|
847
|
+
# 'build_start_timeout' seconds? This only works on newer versions
|
848
|
+
# of Jenkins where JobQueue is exposed in build post response.
|
849
|
+
# Default: false
|
850
|
+
# * +poll_interval+ [Integer] How often should we check with CI
|
851
|
+
# Server while waiting for start. Default: 2 (seconds)
|
852
|
+
# * +progress_proc+ [Proc] A proc that will receive progress notitications. Default: nil
|
853
|
+
# * +completion_proc+ [Proc] A proc that is called <just before>
|
854
|
+
# this method (build) exits. Default: nil
|
855
|
+
#
|
856
|
+
# @return [Integer] build number, or nil if not started (IF TIMEOUT SPECIFIED)
|
857
|
+
# @return [String] HTTP response code (per prev. behavior) (NO TIMEOUT SPECIFIED)
|
858
|
+
#
|
859
|
+
def build(job_name, params={}, opts = {})
|
860
|
+
if opts.nil? || opts.is_a?(FalseClass)
|
861
|
+
opts = {}
|
862
|
+
elsif opts.is_a?(TrueClass)
|
863
|
+
opts = { 'build_start_timeout' => @client_timeout }
|
864
|
+
end
|
865
|
+
|
866
|
+
opts['job_name'] = job_name
|
867
|
+
|
868
|
+
msg = "Building job '#{job_name}'"
|
869
|
+
msg << " with parameters: #{params.inspect}" unless params.empty?
|
870
|
+
@logger.info msg
|
871
|
+
|
872
|
+
if (opts['build_start_timeout'] || 0) > 0
|
873
|
+
# Best-guess build-id
|
874
|
+
# This is only used if we go the old-way below... but we can use this number to detect if multiple
|
875
|
+
# builds were queued
|
876
|
+
current_build_id = get_current_build_number(job_name)
|
877
|
+
expected_build_id = current_build_id > 0 ? current_build_id + 1 : 1
|
878
|
+
end
|
879
|
+
|
880
|
+
if (params.nil? or params.empty?)
|
881
|
+
response = @client.api_post_request("/job/#{path_encode job_name}/build",
|
882
|
+
{},
|
883
|
+
true)
|
884
|
+
else
|
885
|
+
response = @client.api_post_request("/job/#{path_encode job_name}/buildWithParameters",
|
886
|
+
params,
|
887
|
+
true)
|
888
|
+
end
|
889
|
+
|
890
|
+
if (opts['build_start_timeout'] || 0) > 0
|
891
|
+
if @client.compare_versions(@client.get_jenkins_version, JENKINS_QUEUE_ID_SUPPORT_VERSION) >= 0
|
892
|
+
return get_build_id_from_queue(response, expected_build_id, opts)
|
893
|
+
else
|
894
|
+
return get_build_id_the_old_way(expected_build_id, opts)
|
895
|
+
end
|
896
|
+
else
|
897
|
+
return response.code
|
898
|
+
end
|
899
|
+
end
|
900
|
+
|
901
|
+
def get_build_id_from_queue(response, expected_build_id, opts)
|
902
|
+
# If we get this far the API hasn't detected an error response (it would raise Exception)
|
903
|
+
# So no need to check response code
|
904
|
+
# Obtain the queue ID from the location
|
905
|
+
# header and wait till the build is moved to one of the executors and a
|
906
|
+
# build number is assigned
|
907
|
+
build_start_timeout = opts['build_start_timeout']
|
908
|
+
poll_interval = opts['poll_interval'] || 2
|
909
|
+
poll_interval = 1 if poll_interval < 1
|
910
|
+
progress_proc = opts['progress_proc']
|
911
|
+
completion_proc = opts['completion_proc']
|
912
|
+
job_name = opts['job_name']
|
913
|
+
|
914
|
+
if response["location"]
|
915
|
+
task_id_match = response["location"].match(/\/item\/(\d*)\//)
|
916
|
+
task_id = task_id_match.nil? ? nil : task_id_match[1]
|
917
|
+
unless task_id.nil?
|
918
|
+
@logger.info "Job queued for #{job_name}, will wait up to #{build_start_timeout} seconds for build to start..."
|
919
|
+
|
920
|
+
# Let progress proc know we've queued the build
|
921
|
+
progress_proc.call(build_start_timeout, 0, 0) if progress_proc
|
922
|
+
|
923
|
+
# Wait for the build to start
|
924
|
+
begin
|
925
|
+
start = Time.now.to_i
|
926
|
+
Timeout::timeout(build_start_timeout) do
|
927
|
+
started = false
|
928
|
+
attempts = 0
|
929
|
+
|
930
|
+
while !started
|
931
|
+
# Don't really care about the response... if we get thru here, then it must have worked.
|
932
|
+
# Jenkins will return 404's until the job starts
|
933
|
+
queue_item = @client.queue.get_item_by_id(task_id)
|
934
|
+
|
935
|
+
if queue_item['executable'].nil?
|
936
|
+
# Job not started yet
|
937
|
+
attempts += 1
|
938
|
+
|
939
|
+
progress_proc.call(build_start_timeout, (Time.now.to_i - start), attempts) if progress_proc
|
940
|
+
# Every 5 attempts (~10 seconds)
|
941
|
+
@logger.info "Still waiting..." if attempts % 5 == 0
|
942
|
+
|
943
|
+
sleep poll_interval
|
944
|
+
else
|
945
|
+
build_number = queue_item['executable']['number']
|
946
|
+
completion_proc.call(build_number, false) if completion_proc
|
947
|
+
|
948
|
+
return build_number
|
949
|
+
end
|
950
|
+
end
|
951
|
+
end
|
952
|
+
rescue Timeout::Error
|
953
|
+
# Well, we waited - and the job never started building
|
954
|
+
# Attempt to kill off queued job (if flag set)
|
955
|
+
if opts['cancel_on_build_start_timeout']
|
956
|
+
@logger.info "Job for '#{job_name}' did not start in a timely manner, attempting to cancel pending build..."
|
957
|
+
|
958
|
+
begin
|
959
|
+
@client.api_post_request("/queue/cancelItem?id=#{task_id}")
|
960
|
+
@logger.info "Job cancelled"
|
961
|
+
completion_proc.call(nil, true) if completion_proc
|
962
|
+
rescue JenkinsApi::Exceptions::ApiException => e
|
963
|
+
completion_proc.call(nil, false) if completion_proc
|
964
|
+
@logger.warn "Error while attempting to cancel pending job for '#{job_name}'. #{e.class} #{e}"
|
965
|
+
raise
|
966
|
+
end
|
967
|
+
else
|
968
|
+
@logger.info "Jenkins build for '#{job_name}' failed to start in a timely manner"
|
969
|
+
completion_proc.call(nil, false) if completion_proc
|
970
|
+
end
|
971
|
+
|
972
|
+
# Old version used to throw timeout error, so we should let that go thru now
|
973
|
+
raise
|
974
|
+
rescue JenkinsApi::Exceptions::ApiException => e
|
975
|
+
# Jenkins Api threw an error at us
|
976
|
+
completion_proc.call(nil, false) if completion_proc
|
977
|
+
@logger.warn "Problem while waiting for '#{job_name}' build to start. #{e.class} #{e}"
|
978
|
+
raise
|
979
|
+
end
|
980
|
+
else
|
981
|
+
@logger.warn "Jenkins did not return a queue_id for '#{job_name}' build (location: #{response['location']})"
|
982
|
+
return get_build_id_the_old_way(expected_build_id, opts)
|
983
|
+
end
|
984
|
+
else
|
985
|
+
@logger.warn "Jenkins did not return a location header for '#{job_name}' build"
|
986
|
+
return get_build_id_the_old_way(expected_build_id, opts)
|
987
|
+
end
|
988
|
+
end
|
989
|
+
private :get_build_id_from_queue
|
990
|
+
|
991
|
+
def get_build_id_the_old_way(expected_build_id, opts)
|
992
|
+
# Try to wait until the build starts so we can mimic queue
|
993
|
+
# Wait for the build to start
|
994
|
+
build_start_timeout = opts['build_start_timeout']
|
995
|
+
poll_interval = opts['poll_interval'] || 2
|
996
|
+
poll_interval = 1 if poll_interval < 1
|
997
|
+
progress_proc = opts['progress_proc']
|
998
|
+
completion_proc = opts['completion_proc']
|
999
|
+
job_name = opts['job_name']
|
1000
|
+
|
1001
|
+
@logger.info "Build requested for '#{job_name}', will wait up to #{build_start_timeout} seconds for build to start..."
|
1002
|
+
|
1003
|
+
# Let progress proc know we've queued the build
|
1004
|
+
progress_proc.call(build_start_timeout, 0, 0) if progress_proc
|
1005
|
+
|
1006
|
+
begin
|
1007
|
+
start = Time.now.to_i
|
1008
|
+
Timeout::timeout(build_start_timeout) do
|
1009
|
+
attempts = 0
|
1010
|
+
|
1011
|
+
while true
|
1012
|
+
attempts += 1
|
1013
|
+
|
1014
|
+
# Don't really care about the response... if we get thru here, then it must have worked.
|
1015
|
+
# Jenkins will return 404's until the job starts
|
1016
|
+
begin
|
1017
|
+
get_build_details(job_name, expected_build_id)
|
1018
|
+
completion_proc.call(expected_build_id, false) if completion_proc
|
1019
|
+
|
1020
|
+
return expected_build_id
|
1021
|
+
rescue JenkinsApi::Exceptions::NotFound => e
|
1022
|
+
progress_proc.call(build_start_timeout, (Time.now.to_i - start), attempts) if progress_proc
|
1023
|
+
|
1024
|
+
# Every 5 attempts (~10 seconds)
|
1025
|
+
@logger.info "Still waiting..." if attempts % 5 == 0
|
1026
|
+
|
1027
|
+
sleep poll_interval
|
1028
|
+
end
|
1029
|
+
end
|
1030
|
+
end
|
1031
|
+
rescue Timeout::Error
|
1032
|
+
# Well, we waited - and the job never started building
|
1033
|
+
# Now we need to raise an exception so that the build can be officially failed
|
1034
|
+
completion_proc.call(nil, false) if completion_proc
|
1035
|
+
@logger.info "Jenkins '#{job_name}' build failed to start in a timely manner"
|
1036
|
+
|
1037
|
+
# Old version used to propagate timeout error
|
1038
|
+
raise
|
1039
|
+
rescue JenkinsApi::Exceptions::ApiException => e
|
1040
|
+
completion_proc.call(nil, false) if completion_proc
|
1041
|
+
# Jenkins Api threw an error at us
|
1042
|
+
@logger.warn "Problem while waiting for '#{job_name}' build ##{expected_build_id} to start. #{e.class} #{e}"
|
1043
|
+
raise
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
private :get_build_id_the_old_way
|
1047
|
+
|
1048
|
+
# Programatically schedule SCM polling for the specified job
|
1049
|
+
#
|
1050
|
+
# @param job_name [String] the name of the job
|
1051
|
+
#
|
1052
|
+
# @return [String] the response code from the HTTP post request
|
1053
|
+
#
|
1054
|
+
def poll(job_name)
|
1055
|
+
@logger.info "Polling SCM changes for job '#{job_name}'"
|
1056
|
+
@client.api_post_request("/job/#{job_name}/polling")
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
# Enable a job given the name of the job
|
1060
|
+
#
|
1061
|
+
# @param [String] job_name
|
1062
|
+
#
|
1063
|
+
def enable(job_name)
|
1064
|
+
@logger.info "Enabling job '#{job_name}'"
|
1065
|
+
@client.api_post_request("/job/#{path_encode job_name}/enable")
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
# Disable a job given the name of the job
|
1069
|
+
#
|
1070
|
+
# @param [String] job_name
|
1071
|
+
#
|
1072
|
+
def disable(job_name)
|
1073
|
+
@logger.info "Disabling job '#{job_name}'"
|
1074
|
+
@client.api_post_request("/job/#{path_encode job_name}/disable")
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
# Obtain the configuration stored in config.xml of a specific job
|
1078
|
+
#
|
1079
|
+
# @param [String] job_name
|
1080
|
+
#
|
1081
|
+
# @return [String] XML Config.xml of the job
|
1082
|
+
#
|
1083
|
+
def get_config(job_name)
|
1084
|
+
@logger.info "Obtaining the config.xml of '#{job_name}'"
|
1085
|
+
@client.get_config("/job/#{path_encode job_name}")
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
# Post the configuration of a job given the job name and the config.xml
|
1089
|
+
#
|
1090
|
+
# @param [String] job_name
|
1091
|
+
# @param [String] xml
|
1092
|
+
#
|
1093
|
+
# @return [String] response_code return code from HTTP POST
|
1094
|
+
#
|
1095
|
+
def post_config(job_name, xml)
|
1096
|
+
@logger.info "Posting the config.xml of '#{job_name}'"
|
1097
|
+
@client.post_config("/job/#{path_encode job_name}/config.xml", xml)
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
# Obtain the test results for a specific build of a job
|
1101
|
+
#
|
1102
|
+
# @param [String] job_name
|
1103
|
+
# @param [Number] build_num
|
1104
|
+
#
|
1105
|
+
def get_test_results(job_name, build_num)
|
1106
|
+
build_num = get_current_build_number(job_name) if build_num == 0
|
1107
|
+
@logger.info "Obtaining the test results of '#{job_name}'" +
|
1108
|
+
" Build ##{build_num}"
|
1109
|
+
@client.api_get_request("/job/#{path_encode job_name}/#{build_num}/testReport")
|
1110
|
+
rescue Exceptions::NotFound
|
1111
|
+
# Not found is acceptable, as not all builds will have test results
|
1112
|
+
# and this is what jenkins throws at us in that case
|
1113
|
+
nil
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
# Obtain the plugin results for a specific build of a job
|
1117
|
+
#
|
1118
|
+
# @param [String] job_name
|
1119
|
+
# @param [Number] build_num
|
1120
|
+
# @param [String] plugin_name
|
1121
|
+
#
|
1122
|
+
def get_plugin_results(job_name, build_num, plugin_name)
|
1123
|
+
build_num = get_current_build_number(job_name) if build_num == 0
|
1124
|
+
@logger.info "Obtaining the '#{plugin_name}' plugin results of '#{job_name}'" +
|
1125
|
+
" Build ##{build_num}"
|
1126
|
+
@client.api_get_request("/job/#{path_encode job_name}/#{build_num}/#{plugin_name}Result")
|
1127
|
+
rescue Exceptions::NotFound
|
1128
|
+
# Not found is acceptable, as not all builds will have plugin results
|
1129
|
+
# and this is what jenkins throws at us in that case
|
1130
|
+
nil
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
# Obtain detailed build info for a job
|
1134
|
+
#
|
1135
|
+
# @param [String] job_name
|
1136
|
+
# @param [Number] build_num
|
1137
|
+
#
|
1138
|
+
def get_build_details(job_name, build_num)
|
1139
|
+
build_num = get_current_build_number(job_name) if build_num == 0
|
1140
|
+
@logger.info "Obtaining the build details of '#{job_name}'" +
|
1141
|
+
" Build ##{build_num}"
|
1142
|
+
|
1143
|
+
@client.api_get_request("/job/#{path_encode job_name}/#{build_num}/")
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
# Change the description of a specific job
|
1147
|
+
#
|
1148
|
+
# @param [String] job_name
|
1149
|
+
# @param [String] description
|
1150
|
+
#
|
1151
|
+
# @return [String] response_code return code from HTTP POST
|
1152
|
+
#
|
1153
|
+
def change_description(job_name, description)
|
1154
|
+
@logger.info "Changing the description of '#{job_name}' to '#{description}'"
|
1155
|
+
xml = get_config(job_name)
|
1156
|
+
n_xml = Nokogiri::XML(xml)
|
1157
|
+
desc = n_xml.xpath("//description").first
|
1158
|
+
desc.content = "#{description}"
|
1159
|
+
xml_modified = n_xml.to_xml
|
1160
|
+
post_config(job_name, xml_modified)
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
# Block the build of the job when downstream is building
|
1164
|
+
#
|
1165
|
+
# @param [String] job_name
|
1166
|
+
#
|
1167
|
+
# @return [String] response_code return code from HTTP POST
|
1168
|
+
#
|
1169
|
+
def block_build_when_downstream_building(job_name)
|
1170
|
+
@logger.info "Blocking builds of '#{job_name}' when downstream" +
|
1171
|
+
" projects are building"
|
1172
|
+
xml = get_config(job_name)
|
1173
|
+
n_xml = Nokogiri::XML(xml)
|
1174
|
+
node = n_xml.xpath("//blockBuildWhenDownstreamBuilding").first
|
1175
|
+
if node.content == "false"
|
1176
|
+
node.content = "true"
|
1177
|
+
xml_modified = n_xml.to_xml
|
1178
|
+
post_config(job_name, xml_modified)
|
1179
|
+
end
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
# Unblock the build of the job when downstream is building
|
1183
|
+
#
|
1184
|
+
# @param [String] job_name
|
1185
|
+
#
|
1186
|
+
# @return [String] response_code return code from HTTP POST
|
1187
|
+
#
|
1188
|
+
def unblock_build_when_downstream_building(job_name)
|
1189
|
+
@logger.info "Unblocking builds of '#{job_name}' when downstream" +
|
1190
|
+
" projects are building"
|
1191
|
+
xml = get_config(job_name)
|
1192
|
+
n_xml = Nokogiri::XML(xml)
|
1193
|
+
node = n_xml.xpath("//blockBuildWhenDownstreamBuilding").first
|
1194
|
+
if node.content == "true"
|
1195
|
+
node.content = "false"
|
1196
|
+
xml_modified = n_xml.to_xml
|
1197
|
+
post_config(job_name, xml_modified)
|
1198
|
+
end
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
# Block the build of the job when upstream is building
|
1202
|
+
#
|
1203
|
+
# @param [String] job_name
|
1204
|
+
#
|
1205
|
+
# @return [String] response_code return code from HTTP POST
|
1206
|
+
#
|
1207
|
+
def block_build_when_upstream_building(job_name)
|
1208
|
+
@logger.info "Blocking builds of '#{job_name}' when upstream" +
|
1209
|
+
" projects are building"
|
1210
|
+
xml = get_config(job_name)
|
1211
|
+
n_xml = Nokogiri::XML(xml)
|
1212
|
+
node = n_xml.xpath("//blockBuildWhenUpstreamBuilding").first
|
1213
|
+
if node.content == "false"
|
1214
|
+
node.content = "true"
|
1215
|
+
xml_modified = n_xml.to_xml
|
1216
|
+
post_config(job_name, xml_modified)
|
1217
|
+
end
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
# Unblock the build of the job when upstream is building
|
1221
|
+
#
|
1222
|
+
# @param [String] job_name
|
1223
|
+
#
|
1224
|
+
# @return [String] response_code return code from HTTP POST
|
1225
|
+
#
|
1226
|
+
def unblock_build_when_upstream_building(job_name)
|
1227
|
+
@logger.info "Unblocking builds of '#{job_name}' when upstream" +
|
1228
|
+
" projects are building"
|
1229
|
+
xml = get_config(job_name)
|
1230
|
+
n_xml = Nokogiri::XML(xml)
|
1231
|
+
node = n_xml.xpath("//blockBuildWhenUpstreamBuilding").first
|
1232
|
+
if node.content == "true"
|
1233
|
+
node.content = "false"
|
1234
|
+
xml_modified = n_xml.to_xml
|
1235
|
+
post_config(job_name, xml_modified)
|
1236
|
+
end
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
# Allow or disable concurrent build execution
|
1240
|
+
#
|
1241
|
+
# @param [String] job_name
|
1242
|
+
# @param [Bool] option true or false
|
1243
|
+
#
|
1244
|
+
# @return [String] response_code return code from HTTP POST
|
1245
|
+
#
|
1246
|
+
def execute_concurrent_builds(job_name, option)
|
1247
|
+
@logger.info "Setting the concurrent build execution option of" +
|
1248
|
+
" '#{job_name}' to #{option}"
|
1249
|
+
xml = get_config(job_name)
|
1250
|
+
n_xml = Nokogiri::XML(xml)
|
1251
|
+
node = n_xml.xpath("//concurrentBuild").first
|
1252
|
+
if node.content != "#{option}"
|
1253
|
+
node.content = option == true ? "true" : "false"
|
1254
|
+
xml_modified = n_xml.to_xml
|
1255
|
+
post_config(job_name, xml_modified)
|
1256
|
+
end
|
1257
|
+
end
|
1258
|
+
|
1259
|
+
# Obtain the build parameters of a job. It returns an array of hashes with
|
1260
|
+
# details of job params.
|
1261
|
+
#
|
1262
|
+
# @param [String] job_name
|
1263
|
+
#
|
1264
|
+
# @return [Array] params_array Array of parameters for the given job
|
1265
|
+
#
|
1266
|
+
def get_build_params(job_name)
|
1267
|
+
@logger.info "Obtaining the build params of '#{job_name}'"
|
1268
|
+
xml = get_config(job_name)
|
1269
|
+
n_xml = Nokogiri::XML(xml)
|
1270
|
+
params = n_xml.xpath("//parameterDefinitions").first
|
1271
|
+
params_array = []
|
1272
|
+
if params
|
1273
|
+
params.children.each do |param|
|
1274
|
+
param_hash = {}
|
1275
|
+
case param.name
|
1276
|
+
when "hudson.model.StringParameterDefinition",
|
1277
|
+
"hudson.model.BooleanParameterDefinition",
|
1278
|
+
"hudson.model.TextParameterDefinition",
|
1279
|
+
"hudson.model.PasswordParameterDefinition"
|
1280
|
+
param_hash[:type] = 'string' if param.name =~ /string/i
|
1281
|
+
param_hash[:type] = 'boolean' if param.name =~ /boolean/i
|
1282
|
+
param_hash[:type] = 'text' if param.name =~ /text/i
|
1283
|
+
param_hash[:type] = 'password' if param.name =~ /password/i
|
1284
|
+
param.children.each do |value|
|
1285
|
+
param_hash[:name] = value.content if value.name == "name"
|
1286
|
+
if value.name == "description"
|
1287
|
+
param_hash[:description] = value.content
|
1288
|
+
end
|
1289
|
+
if value.name == "defaultValue"
|
1290
|
+
param_hash[:default] = value.content
|
1291
|
+
end
|
1292
|
+
end
|
1293
|
+
when "hudson.model.RunParameterDefinition"
|
1294
|
+
param_hash[:type] = 'run'
|
1295
|
+
param.children.each do |value|
|
1296
|
+
param_hash[:name] = value.content if value.name == "name"
|
1297
|
+
if value.name == "description"
|
1298
|
+
param_hash[:description] = value.content
|
1299
|
+
end
|
1300
|
+
if value.name == "projectName"
|
1301
|
+
param_hash[:project] = value.content
|
1302
|
+
end
|
1303
|
+
end
|
1304
|
+
when "hudson.model.FileParameterDefinition"
|
1305
|
+
param_hash[:type] = 'file'
|
1306
|
+
param.children.each do |value|
|
1307
|
+
param_hash[:name] = value.content if value.name == "name"
|
1308
|
+
if value.name == "description"
|
1309
|
+
param_hash[:description] = value.content
|
1310
|
+
end
|
1311
|
+
end
|
1312
|
+
when "hudson.scm.listtagsparameter.ListSubversionTagsParameterDefinition"
|
1313
|
+
param_hash[:type] = 'list_tags'
|
1314
|
+
param.children.each do |value|
|
1315
|
+
if value.name == "name"
|
1316
|
+
param_hash[:name] = value.content
|
1317
|
+
end
|
1318
|
+
if value.name == "description"
|
1319
|
+
param_hash[:description] = value.content
|
1320
|
+
end
|
1321
|
+
if value.name == "tagsDir"
|
1322
|
+
param_hash[:tags_dir] = value.content
|
1323
|
+
end
|
1324
|
+
if value.name == "tagsFilter"
|
1325
|
+
param_hash[:tags_filter] = value.content
|
1326
|
+
end
|
1327
|
+
if value.name == "reverseByDate"
|
1328
|
+
param_hash[:reverse_by_date] = value.content
|
1329
|
+
end
|
1330
|
+
if value.name == "reverseByName"
|
1331
|
+
param_hash[:reverse_by_name] = value.content
|
1332
|
+
end
|
1333
|
+
if value.name == "defaultValue"
|
1334
|
+
param_hash[:default] = value.content
|
1335
|
+
end
|
1336
|
+
param_hash[:max_tags] = value.content if value.name == "maxTags"
|
1337
|
+
param_hash[:uuid] = value.content if value.name == "uuid"
|
1338
|
+
end
|
1339
|
+
when "hudson.model.ChoiceParameterDefinition"
|
1340
|
+
param_hash[:type] = 'choice'
|
1341
|
+
param.children.each do |value|
|
1342
|
+
param_hash[:name] = value.content if value.name == "name"
|
1343
|
+
param_hash[:description] = value.content \
|
1344
|
+
if value.name == "description"
|
1345
|
+
choices = []
|
1346
|
+
if value.name == "choices"
|
1347
|
+
value.children.each do |value_child|
|
1348
|
+
if value_child.name == "a"
|
1349
|
+
value_child.children.each do |choice_child|
|
1350
|
+
choices << choice_child.content.strip \
|
1351
|
+
unless choice_child.content.strip.empty?
|
1352
|
+
end
|
1353
|
+
end
|
1354
|
+
end
|
1355
|
+
end
|
1356
|
+
param_hash[:choices] = choices unless choices.empty?
|
1357
|
+
end
|
1358
|
+
end
|
1359
|
+
params_array << param_hash unless param_hash.empty?
|
1360
|
+
end
|
1361
|
+
end
|
1362
|
+
params_array
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
# Add downstream projects to a specific job given the job name,
|
1366
|
+
# projects to be added as downstream projects, and the threshold
|
1367
|
+
#
|
1368
|
+
# @param [String] job_name
|
1369
|
+
# @param [String] downstream_projects
|
1370
|
+
# @param [String] threshold - failure, success, or unstable
|
1371
|
+
# @param [Boolean] overwrite - true or false
|
1372
|
+
#
|
1373
|
+
# @return [String] response_code return code from HTTP POST
|
1374
|
+
#
|
1375
|
+
def add_downstream_projects(job_name,
|
1376
|
+
downstream_projects,
|
1377
|
+
threshold, overwrite = false)
|
1378
|
+
@logger.info "Adding #{downstream_projects.inspect} as downstream" +
|
1379
|
+
" projects for '#{job_name}' with the threshold of '#{threshold}'" +
|
1380
|
+
" and overwrite option of '#{overwrite}'"
|
1381
|
+
name, ord, col = get_threshold_params(threshold)
|
1382
|
+
xml = get_config(job_name)
|
1383
|
+
n_xml = Nokogiri::XML(xml)
|
1384
|
+
child_projects_node = n_xml.xpath("//childProjects").first
|
1385
|
+
if child_projects_node
|
1386
|
+
if overwrite
|
1387
|
+
child_projects_node.content = "#{downstream_projects}"
|
1388
|
+
else
|
1389
|
+
to_replace = child_projects_node.content +
|
1390
|
+
", #{downstream_projects}"
|
1391
|
+
child_projects_node.content = to_replace
|
1392
|
+
end
|
1393
|
+
else
|
1394
|
+
publisher_node = n_xml.xpath("//publishers").first
|
1395
|
+
build_trigger_node = publisher_node.add_child(
|
1396
|
+
"<hudson.tasks.BuildTrigger/>"
|
1397
|
+
)
|
1398
|
+
child_project_node = build_trigger_node.first.add_child(
|
1399
|
+
"<childProjects>#{downstream_projects}</childProjects>"
|
1400
|
+
)
|
1401
|
+
threshold_node = child_project_node.first.add_next_sibling(
|
1402
|
+
"<threshold/>"
|
1403
|
+
)
|
1404
|
+
threshold_node.first.add_child(
|
1405
|
+
"<name>#{name}</name><ordinal>#{ord}</ordinal><color>#{col}</color>"
|
1406
|
+
)
|
1407
|
+
end
|
1408
|
+
xml_modified = n_xml.to_xml
|
1409
|
+
post_config(job_name, xml_modified)
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
# Remove all downstream projects of a specific job
|
1413
|
+
#
|
1414
|
+
# @param [String] job_name
|
1415
|
+
#
|
1416
|
+
# @return [String] response_code return code from HTTP POST
|
1417
|
+
#
|
1418
|
+
def remove_downstream_projects(job_name)
|
1419
|
+
@logger.info "Removing the downstream projects of '#{job_name}'"
|
1420
|
+
xml = get_config(job_name)
|
1421
|
+
n_xml = Nokogiri::XML(xml)
|
1422
|
+
n_xml.search("//hudson.tasks.BuildTrigger").each do |node|
|
1423
|
+
child_project_trigger = false
|
1424
|
+
node.search("//childProjects").each do |child_node|
|
1425
|
+
child_project_trigger = true
|
1426
|
+
child_node.search("//threshold").each do |threshold_node|
|
1427
|
+
threshold_node.children.each do |threshold_value_node|
|
1428
|
+
threshold_value_node.content = nil
|
1429
|
+
threshold_value_node.remove
|
1430
|
+
end
|
1431
|
+
threshold_node.content = nil
|
1432
|
+
threshold_node.remove
|
1433
|
+
end
|
1434
|
+
child_node.content = nil
|
1435
|
+
child_node.remove
|
1436
|
+
end
|
1437
|
+
node.content = nil
|
1438
|
+
node.remove
|
1439
|
+
end
|
1440
|
+
publisher_node = n_xml.search("//publishers").first
|
1441
|
+
publisher_node.content = nil if publisher_node.children.empty?
|
1442
|
+
xml_modified = n_xml.to_xml
|
1443
|
+
post_config(job_name, xml_modified)
|
1444
|
+
end
|
1445
|
+
|
1446
|
+
# Add upstream projects to a specific job given the job name,
|
1447
|
+
# projects to be added as upstream projects, and the threshold
|
1448
|
+
#
|
1449
|
+
# @param [String] job_name
|
1450
|
+
# @param [String] upstream_projects - separated with comma
|
1451
|
+
# @param [String] threshold - failure, success, or unstable
|
1452
|
+
# @param [Boolean] overwrite - true or false
|
1453
|
+
#
|
1454
|
+
# @return [String] response_code return code from HTTP POST
|
1455
|
+
#
|
1456
|
+
def add_upstream_projects(job_name,
|
1457
|
+
upstream_projects,
|
1458
|
+
threshold, overwrite = false)
|
1459
|
+
@logger.info "Adding #{upstream_projects.inspect} as upstream" +
|
1460
|
+
" projects for '#{job_name}' with the threshold of '#{threshold}'" +
|
1461
|
+
" and overwrite option of '#{overwrite}'"
|
1462
|
+
name, ord, col = get_threshold_params(threshold)
|
1463
|
+
xml = get_config(job_name)
|
1464
|
+
n_xml = Nokogiri::XML(xml)
|
1465
|
+
upstream_projects_node = n_xml.xpath("//upstreamProjects").first
|
1466
|
+
if upstream_projects_node
|
1467
|
+
if overwrite
|
1468
|
+
upstream_projects_node.content = "#{upstream_projects}"
|
1469
|
+
else
|
1470
|
+
to_replace = upstream_projects_node.content +
|
1471
|
+
", #{upstream_projects}"
|
1472
|
+
upstream_projects_node.content = to_replace
|
1473
|
+
end
|
1474
|
+
else
|
1475
|
+
triggers_node = n_xml.xpath("//triggers").first
|
1476
|
+
reverse_build_trigger_node = triggers_node.add_child(
|
1477
|
+
"<jenkins.triggers.ReverseBuildTrigger/>"
|
1478
|
+
)
|
1479
|
+
reverse_build_trigger_node.first.add_child(
|
1480
|
+
"<spec/>"
|
1481
|
+
)
|
1482
|
+
reverse_build_trigger_node.first.add_child(
|
1483
|
+
"<upstreamProjects>#{upstream_projects}</upstreamProjects>"
|
1484
|
+
)
|
1485
|
+
threshold_node = reverse_build_trigger_node.first.add_child(
|
1486
|
+
"<threshold/>"
|
1487
|
+
)
|
1488
|
+
threshold_node.first.add_child(
|
1489
|
+
"<name>#{name}</name><ordinal>#{ord}</ordinal><color>#{col}</color>"
|
1490
|
+
)
|
1491
|
+
end
|
1492
|
+
xml_modified = n_xml.to_xml
|
1493
|
+
post_config(job_name, xml_modified)
|
1494
|
+
end
|
1495
|
+
|
1496
|
+
# Remove all upstream projects of a specific job
|
1497
|
+
#
|
1498
|
+
# @param [String] job_name
|
1499
|
+
#
|
1500
|
+
# @return [String] response_code return code from HTTP POST
|
1501
|
+
#
|
1502
|
+
def remove_upstream_projects(job_name)
|
1503
|
+
@logger.info "Removing the upstream projects of '#{job_name}'"
|
1504
|
+
xml = get_config(job_name)
|
1505
|
+
n_xml = Nokogiri::XML(xml)
|
1506
|
+
n_xml.search("//jenkins.triggers.ReverseBuildTrigger").remove
|
1507
|
+
xml_modified = n_xml.to_xml
|
1508
|
+
post_config(job_name, xml_modified)
|
1509
|
+
end
|
1510
|
+
|
1511
|
+
# Resctrict the given job to a specific node
|
1512
|
+
#
|
1513
|
+
# @param [String] job_name
|
1514
|
+
# @param [String] node_name
|
1515
|
+
#
|
1516
|
+
# @return [String] response_code return code from HTTP POST
|
1517
|
+
#
|
1518
|
+
def restrict_to_node(job_name, node_name)
|
1519
|
+
@logger.info "Restricting '#{job_name}' to '#{node_name}' node"
|
1520
|
+
xml = get_config(job_name)
|
1521
|
+
n_xml = Nokogiri::XML(xml)
|
1522
|
+
if (node = n_xml.xpath("//assignedNode").first)
|
1523
|
+
node.content = node_name
|
1524
|
+
else
|
1525
|
+
project = n_xml.xpath("//scm").first
|
1526
|
+
project.add_next_sibling("<assignedNode>#{node_name}</assignedNode>")
|
1527
|
+
roam_node = n_xml.xpath("//canRoam").first
|
1528
|
+
roam_node.content = "false"
|
1529
|
+
end
|
1530
|
+
xml_modified = n_xml.to_xml
|
1531
|
+
post_config(job_name, xml_modified)
|
1532
|
+
end
|
1533
|
+
|
1534
|
+
# Unchain any existing chain between given job names
|
1535
|
+
#
|
1536
|
+
# @param [Array] job_names Array of job names to be unchained
|
1537
|
+
#
|
1538
|
+
def unchain(job_names)
|
1539
|
+
@logger.info "Unchaining jobs: #{job_names.inspect}"
|
1540
|
+
job_names.each { |job| remove_downstream_projects(job) }
|
1541
|
+
end
|
1542
|
+
|
1543
|
+
# Chain the jobs given based on specified criteria
|
1544
|
+
#
|
1545
|
+
# @param [Array] job_names Array of job names to be chained
|
1546
|
+
# @param [String] threshold threshold for running the next job
|
1547
|
+
# @param [Array] criteria criteria which should be applied for
|
1548
|
+
# picking the jobs for the chain
|
1549
|
+
# @param [Integer] parallel Number of jobs that should be considered
|
1550
|
+
# for parallel run
|
1551
|
+
#
|
1552
|
+
# @return [Array] job_names Names of jobs that are in the top of the
|
1553
|
+
# chain
|
1554
|
+
def chain(job_names, threshold, criteria, parallel = 1)
|
1555
|
+
raise "Parallel jobs should be at least 1" if parallel < 1
|
1556
|
+
unchain(job_names)
|
1557
|
+
|
1558
|
+
@logger.info "Chaining jobs: #{job_names.inspect}" +
|
1559
|
+
" with threshold of '#{threshold}' and criteria as '#{criteria}'" +
|
1560
|
+
" with #{parallel} number of parallel jobs"
|
1561
|
+
filtered_job_names = []
|
1562
|
+
if criteria.include?("all") || criteria.empty?
|
1563
|
+
filtered_job_names = job_names
|
1564
|
+
else
|
1565
|
+
job_names.each do |job|
|
1566
|
+
filtered_job_names << job if criteria.include?(
|
1567
|
+
@client.job.get_current_build_status(job)
|
1568
|
+
)
|
1569
|
+
end
|
1570
|
+
end
|
1571
|
+
|
1572
|
+
filtered_job_names.each_with_index do |job_name, index|
|
1573
|
+
break if index >= (filtered_job_names.length - parallel)
|
1574
|
+
@client.job.add_downstream_projects(
|
1575
|
+
job_name, filtered_job_names[index + parallel], threshold, true
|
1576
|
+
)
|
1577
|
+
end
|
1578
|
+
if parallel > filtered_job_names.length
|
1579
|
+
parallel = filtered_job_names.length
|
1580
|
+
end
|
1581
|
+
filtered_job_names[0..parallel-1]
|
1582
|
+
end
|
1583
|
+
|
1584
|
+
# Get a list of promoted builds for given job
|
1585
|
+
#
|
1586
|
+
# @param [String] job_name
|
1587
|
+
# @return [Hash] Hash map of promitions and the promoted builds. Promotions that didn't took place yet
|
1588
|
+
# return nil
|
1589
|
+
def get_promotions(job_name)
|
1590
|
+
result = {}
|
1591
|
+
|
1592
|
+
@logger.info "Obtaining the promotions of '#{job_name}'"
|
1593
|
+
response_json = @client.api_get_request("/job/#{job_name}/promotion")
|
1594
|
+
|
1595
|
+
response_json["processes"].each do |promotion|
|
1596
|
+
@logger.info "Getting promotion details of '#{promotion['name']}'"
|
1597
|
+
|
1598
|
+
if promotion['color'] == 'notbuilt'
|
1599
|
+
result[promotion['name']] = nil
|
1600
|
+
else
|
1601
|
+
promo_json = @client.api_get_request("/job/#{job_name}/promotion/latest/#{promotion['name']}")
|
1602
|
+
result[promotion['name']] = promo_json['target']['number']
|
1603
|
+
end
|
1604
|
+
end
|
1605
|
+
|
1606
|
+
result
|
1607
|
+
end
|
1608
|
+
|
1609
|
+
|
1610
|
+
# Create a new promotion process
|
1611
|
+
#
|
1612
|
+
# This must be called before set/get promote config can be used on a process
|
1613
|
+
#
|
1614
|
+
# Must be called after updating the job's config
|
1615
|
+
# @param [String] job_name
|
1616
|
+
# @param [String] process The process name
|
1617
|
+
# @return [String] Process config
|
1618
|
+
def init_promote_process(job_name, process, config)
|
1619
|
+
@logger.info "Creating new process #{process} for job #{job_name}"
|
1620
|
+
@client.post_config("/job/#{job_name}/promotion/createProcess?name=#{process}", config)
|
1621
|
+
end
|
1622
|
+
|
1623
|
+
|
1624
|
+
# Get a job's promotion config
|
1625
|
+
#
|
1626
|
+
# @param [String] job_name
|
1627
|
+
# @param [String] process The process name
|
1628
|
+
# @return [String] Promote config
|
1629
|
+
def get_promote_config(job_name, process)
|
1630
|
+
@logger.info "Getting promote config for job '#{job_name}' process '#{process}'"
|
1631
|
+
@client.get_config("/job/#{job_name}/promotion/process/#{process}/config.xml")
|
1632
|
+
end
|
1633
|
+
|
1634
|
+
# Set a job's promotion config
|
1635
|
+
#
|
1636
|
+
# @param [String] job_name
|
1637
|
+
# @param [String] process The process name
|
1638
|
+
# @param [String] Job config
|
1639
|
+
# @return nil
|
1640
|
+
def set_promote_config(job_name, process, config)
|
1641
|
+
@logger.info "Setting promote config for job '#{job_name}' process '#{process}' to #{config}"
|
1642
|
+
@client.post_config("/job/#{job_name}/promotion/process/#{process}/config.xml", config)
|
1643
|
+
end
|
1644
|
+
|
1645
|
+
# Delete a job's promotion config
|
1646
|
+
#
|
1647
|
+
# @param [String] job_name
|
1648
|
+
# @param [String] process The process name
|
1649
|
+
# @return nil
|
1650
|
+
def delete_promote_config(job_name, process)
|
1651
|
+
@logger.info "Deleting promote config for job '#{job_name}' process '#{process}'"
|
1652
|
+
@client.post_config("/job/#{job_name}/promotion/process/#{process}/doDelete")
|
1653
|
+
end
|
1654
|
+
|
1655
|
+
#A Method to find artifacts path from the Current Build
|
1656
|
+
#
|
1657
|
+
# @param [String] job_name
|
1658
|
+
# @param [Integer] build_number
|
1659
|
+
# defaults to latest build
|
1660
|
+
#
|
1661
|
+
def find_artifact(job_name, build_number = 0)
|
1662
|
+
find_artifacts(job_name, build_number).first
|
1663
|
+
end
|
1664
|
+
|
1665
|
+
#A Method to check artifact exists path from the Current Build
|
1666
|
+
#
|
1667
|
+
# @param [String] job_name
|
1668
|
+
# @param [Integer] build_number
|
1669
|
+
# defaults to latest build
|
1670
|
+
#
|
1671
|
+
def artifact_exists?(job_name, build_number = 0)
|
1672
|
+
begin
|
1673
|
+
artifact_path(job_name: job_name, build_number: build_number)
|
1674
|
+
|
1675
|
+
return true
|
1676
|
+
rescue Exception => e
|
1677
|
+
return false
|
1678
|
+
end
|
1679
|
+
end
|
1680
|
+
|
1681
|
+
# Find the artifacts for build_number of job_name, defaulting to current job
|
1682
|
+
#
|
1683
|
+
# @param [String] job_name
|
1684
|
+
# @param [Integer] build_number Optional build number
|
1685
|
+
# @return [String, Hash] JSON response from Jenkins
|
1686
|
+
#
|
1687
|
+
def find_artifacts(job_name, build_number = nil)
|
1688
|
+
response_json = get_build_details(job_name, build_number)
|
1689
|
+
artifact_path(build_details: response_json).map do |p|
|
1690
|
+
path_encode("#{response_json['url']}artifact/#{p['relativePath']}")
|
1691
|
+
end
|
1692
|
+
end
|
1693
|
+
|
1694
|
+
# Find the artifacts for the current job
|
1695
|
+
#
|
1696
|
+
# @param [String] job_name
|
1697
|
+
# @return [String, Hash] JSON response from Jenkins
|
1698
|
+
#
|
1699
|
+
def find_latest_artifacts(job_name)
|
1700
|
+
find_artifacts(job_name)
|
1701
|
+
end
|
1702
|
+
|
1703
|
+
private
|
1704
|
+
|
1705
|
+
# Obtains the threshold params used by jenkins in the XML file
|
1706
|
+
# given the threshold
|
1707
|
+
#
|
1708
|
+
# @param [String] threshold success, failure, or unstable
|
1709
|
+
#
|
1710
|
+
# @return [String] status readable status matching the color
|
1711
|
+
#
|
1712
|
+
def get_threshold_params(threshold)
|
1713
|
+
case threshold
|
1714
|
+
when 'success'
|
1715
|
+
name = 'SUCCESS'
|
1716
|
+
ordinal = 0
|
1717
|
+
color = 'BLUE'
|
1718
|
+
when 'unstable'
|
1719
|
+
name = 'UNSTABLE'
|
1720
|
+
ordinal = 1
|
1721
|
+
color = 'YELLOW'
|
1722
|
+
when 'failure'
|
1723
|
+
name = 'FAILURE'
|
1724
|
+
ordinal = 2
|
1725
|
+
color = 'RED'
|
1726
|
+
end
|
1727
|
+
return name, ordinal, color
|
1728
|
+
end
|
1729
|
+
|
1730
|
+
# This private method builds portion of XML that adds subversion SCM
|
1731
|
+
# to a Job
|
1732
|
+
#
|
1733
|
+
# @param [Hash] params parameters to be used for building XML
|
1734
|
+
# @param [XML] xml Nokogiri XML object
|
1735
|
+
#
|
1736
|
+
def scm_subversion(params, xml)
|
1737
|
+
xml.scm(:class => "hudson.scm.SubversionSCM",
|
1738
|
+
:plugin => "subversion@1.39") {
|
1739
|
+
xml.locations {
|
1740
|
+
xml.send("hudson.scm.SubversionSCM_-ModuleLocation") {
|
1741
|
+
xml.remote "#{params[:scm_url]}"
|
1742
|
+
xml.local "."
|
1743
|
+
}
|
1744
|
+
}
|
1745
|
+
xml.excludedRegions
|
1746
|
+
xml.includedRegions
|
1747
|
+
xml.excludedUsers
|
1748
|
+
xml.excludedRevprop
|
1749
|
+
xml.excludedCommitMessages
|
1750
|
+
xml.workspaceUpdater(:class =>
|
1751
|
+
"hudson.scm.subversion.UpdateUpdater")
|
1752
|
+
}
|
1753
|
+
end
|
1754
|
+
|
1755
|
+
# This private method builds portion of XML that adds CVS SCM to a Job
|
1756
|
+
#
|
1757
|
+
# @param [Hash] params parameters to be used for building XML
|
1758
|
+
# @param [XML] xml Nokogiri XML object
|
1759
|
+
#
|
1760
|
+
def scm_cvs(params, xml)
|
1761
|
+
xml.scm(:class => "hudson.scm.CVSSCM",
|
1762
|
+
:plugin => "cvs@1.6") {
|
1763
|
+
xml.cvsroot "#{params[:scm_url]}"
|
1764
|
+
xml.module "#{params[:scm_module]}"
|
1765
|
+
if params[:scm_branch]
|
1766
|
+
xml.branch "#{params[:scm_branch]}"
|
1767
|
+
else
|
1768
|
+
xml.branch "#{params[:scm_tag]}"
|
1769
|
+
end
|
1770
|
+
xml.canUseUpdate true
|
1771
|
+
xml.useHeadIfNotFound(
|
1772
|
+
"#{params[:scm_use_head_if_tag_not_found]}")
|
1773
|
+
xml.flatten true
|
1774
|
+
if params[:scm_tag]
|
1775
|
+
xml.isTag true
|
1776
|
+
else
|
1777
|
+
xml.isTag false
|
1778
|
+
end
|
1779
|
+
xml.excludedRegions
|
1780
|
+
}
|
1781
|
+
end
|
1782
|
+
|
1783
|
+
# This private method adds portion of XML that adds Git SCM to a Job
|
1784
|
+
#
|
1785
|
+
# @param [Hash] params parameters to be used for building XML
|
1786
|
+
# @param [XML] xml Nokogiri XML object
|
1787
|
+
#
|
1788
|
+
def scm_git(params, xml)
|
1789
|
+
xml.scm(:class => "hudson.plugins.git.GitSCM") {
|
1790
|
+
xml.configVersion "2"
|
1791
|
+
xml.userRemoteConfigs {
|
1792
|
+
xml.send("hudson.plugins.git.UserRemoteConfig") {
|
1793
|
+
xml.name
|
1794
|
+
xml.refspec
|
1795
|
+
xml.credentialsId "#{params[:scm_credentials_id]}"
|
1796
|
+
xml.url "#{params[:scm_url]}"
|
1797
|
+
}
|
1798
|
+
}
|
1799
|
+
xml.branches {
|
1800
|
+
xml.send("hudson.plugins.git.BranchSpec") {
|
1801
|
+
xml.name "#{params[:scm_branch]}"
|
1802
|
+
}
|
1803
|
+
}
|
1804
|
+
xml.disableSubmodules "false"
|
1805
|
+
xml.recursiveSubmodules "false"
|
1806
|
+
xml.doGenerateSubmoduleConfigurations "false"
|
1807
|
+
xml.authorOrCommitter "false"
|
1808
|
+
xml.clean "false"
|
1809
|
+
xml.wipeOutWorkspace "false"
|
1810
|
+
xml.pruneBranches "false"
|
1811
|
+
xml.remotePoll "false"
|
1812
|
+
xml.ignoreNotifyCommit "false"
|
1813
|
+
xml.useShallowClone "false"
|
1814
|
+
xml.buildChooser(:class =>
|
1815
|
+
"hudson.plugins.git.util.DefaultBuildChooser")
|
1816
|
+
xml.gitTool params.fetch(:scm_git_tool) { "Default" }
|
1817
|
+
xml.submoduleCfg(:class => "list")
|
1818
|
+
xml.relativeTargetDir
|
1819
|
+
xml.reference
|
1820
|
+
xml.excludedRegions
|
1821
|
+
xml.excludedUsers
|
1822
|
+
xml.gitConfigName
|
1823
|
+
xml.gitConfigEmail
|
1824
|
+
xml.skipTag "false"
|
1825
|
+
xml.includedRegions
|
1826
|
+
xml.scmName
|
1827
|
+
}
|
1828
|
+
end
|
1829
|
+
|
1830
|
+
# Method for creating portion of xml that builds Skype notification
|
1831
|
+
# Use this option only when you have the Skype plugin installed and
|
1832
|
+
# everything is set up properly
|
1833
|
+
#
|
1834
|
+
# @param [Hash] params Parameters for adding skype notificaiton. For the
|
1835
|
+
# options in this params Hash refer to create_freestyle
|
1836
|
+
# @param [XML] xml Main xml to attach the skype portion.
|
1837
|
+
#
|
1838
|
+
def skype_notification(params, xml)
|
1839
|
+
params[:skype_strategy] = case params[:skype_strategy]
|
1840
|
+
when "all"
|
1841
|
+
"ALL"
|
1842
|
+
when "failure"
|
1843
|
+
"ANY_FAILURE"
|
1844
|
+
when "failure_and_fixed"
|
1845
|
+
"FAILURE_AND_FIXED"
|
1846
|
+
when "change"
|
1847
|
+
"STATECHANGE_ONLY"
|
1848
|
+
else
|
1849
|
+
"STATECHANGE_ONLY"
|
1850
|
+
end
|
1851
|
+
|
1852
|
+
params[:skype_notify_on_build_start] = false if params[:skype_notify_on_build_start].nil?
|
1853
|
+
params[:skype_notify_suspects] = false if params[:skype_notify_suspects].nil?
|
1854
|
+
params[:skype_notify_culprits] = false if params[:skype_notify_culprits].nil?
|
1855
|
+
params[:skype_notify_fixers] = false if params[:skype_notify_fixers].nil?
|
1856
|
+
params[:skype_notify_upstream_committers] = false if params[:skype_notify_upstream_committers].nil?
|
1857
|
+
|
1858
|
+
targets = params[:skype_targets].split(/\s+/)
|
1859
|
+
xml.send("hudson.plugins.skype.im.transport.SkypePublisher") {
|
1860
|
+
xml.targets {
|
1861
|
+
targets.each { |target|
|
1862
|
+
if target =~ /^\*/
|
1863
|
+
# Group Chat
|
1864
|
+
xml.send("hudson.plugins.im.GroupChatIMMessageTarget") {
|
1865
|
+
# Skipe the first * character
|
1866
|
+
xml.value target[1..-1]
|
1867
|
+
xml.notificationOnly false
|
1868
|
+
}
|
1869
|
+
else
|
1870
|
+
# Individual message
|
1871
|
+
xml.send("hudson.plugins.im.DefaultIMMessageTarget") {
|
1872
|
+
xml.value target
|
1873
|
+
}
|
1874
|
+
end
|
1875
|
+
}
|
1876
|
+
}
|
1877
|
+
xml.strategy "#{params[:skype_strategy]}"
|
1878
|
+
xml.notifyOnBuildStart params[:skype_notify_on_build_start]
|
1879
|
+
xml.notifySuspects params[:skype_notify_suspects]
|
1880
|
+
xml.notifyCulprits params[:skype_notify_culprits]
|
1881
|
+
xml.notifyFixers params[:skype_notify_fixers]
|
1882
|
+
xml.notifyUpstreamCommitters params[:skype_notify_upstream_committers]
|
1883
|
+
notification_class = case params[:skype_message]
|
1884
|
+
when "just_summary"
|
1885
|
+
"hudson.plugins.im.build_notify.SummaryOnlyBuildToChatNotifier"
|
1886
|
+
when "summary_and_scm_changes"
|
1887
|
+
"hudson.plugins.im.build_notify.DefaultBuildToChatNotifier"
|
1888
|
+
when "summary_and_build_parameters"
|
1889
|
+
"hudson.plugins.im.build_notify.BuildParametersBuildToChatNotifier"
|
1890
|
+
when "summary_scm_changes_and_failed_tests"
|
1891
|
+
"hudson.plugins.im.build_notify.PrintFailingTestsBuildToChatNotifier"
|
1892
|
+
else
|
1893
|
+
"hudson.plugins.im.build_notify.DefaultBuildToChatNotifier"
|
1894
|
+
end
|
1895
|
+
xml.buildToChatNotifier(:class => notification_class)
|
1896
|
+
xml.matrixMultiplier "ONLY_CONFIGURATIONS"
|
1897
|
+
}
|
1898
|
+
end
|
1899
|
+
|
1900
|
+
# This private method builds portion of XML that adds notification email
|
1901
|
+
# to a Job.
|
1902
|
+
#
|
1903
|
+
# @param [Hash] params parameters to be used for building XML
|
1904
|
+
# @param [XML] xml Nokogiri XML object
|
1905
|
+
#
|
1906
|
+
def notification_email(params, xml)
|
1907
|
+
if params[:notification_email]
|
1908
|
+
xml.send("hudson.tasks.Mailer") {
|
1909
|
+
xml.recipients "#{params[:notification_email]}"
|
1910
|
+
xml.dontNotifyEveryUnstableBuild(
|
1911
|
+
"#{params[:notification_email_for_every_unstable]}")
|
1912
|
+
xml.sendToIndividuals(
|
1913
|
+
"#{params[:notification_email_send_to_individuals]}")
|
1914
|
+
}
|
1915
|
+
end
|
1916
|
+
end
|
1917
|
+
|
1918
|
+
# This private method builds portion of XML that adds child projects
|
1919
|
+
# to a Job.
|
1920
|
+
#
|
1921
|
+
# @param [Hash] params parameters to be used for building XML
|
1922
|
+
# @param [XML] xml Nokogiri XML object
|
1923
|
+
#
|
1924
|
+
def child_projects(params, xml)
|
1925
|
+
xml.send("hudson.tasks.BuildTrigger") {
|
1926
|
+
xml.childProjects "#{params[:child_projects]}"
|
1927
|
+
threshold = params[:child_threshold]
|
1928
|
+
name, ordinal, color = get_threshold_params(threshold)
|
1929
|
+
xml.threshold {
|
1930
|
+
xml.name "#{name}"
|
1931
|
+
xml.ordinal "#{ordinal}"
|
1932
|
+
xml.color "#{color}"
|
1933
|
+
}
|
1934
|
+
}
|
1935
|
+
end
|
1936
|
+
|
1937
|
+
def tree_string tree_value
|
1938
|
+
return nil unless tree_value
|
1939
|
+
"tree=#{tree_value}"
|
1940
|
+
end
|
1941
|
+
|
1942
|
+
# This private method gets the artifact path or throws an exception
|
1943
|
+
#
|
1944
|
+
# @param [Hash] job_name, build_number or build_details object
|
1945
|
+
#
|
1946
|
+
def artifact_path(params)
|
1947
|
+
job_name = params[:job_name]
|
1948
|
+
build_number = params[:build_number] || 0
|
1949
|
+
build_details = params[:build_details]
|
1950
|
+
|
1951
|
+
build_details = get_build_details(job_name, build_number) if build_details.nil?
|
1952
|
+
artifacts = build_details['artifacts']
|
1953
|
+
artifact_paths = []
|
1954
|
+
|
1955
|
+
if artifacts && artifacts.any?
|
1956
|
+
artifact_paths = artifacts.find_all{ |a| a.key?('relativePath') }
|
1957
|
+
end
|
1958
|
+
|
1959
|
+
if artifact_paths.empty?
|
1960
|
+
raise "No artifacts found."
|
1961
|
+
end
|
1962
|
+
artifact_paths
|
1963
|
+
end
|
1964
|
+
end
|
1965
|
+
end
|
1966
|
+
end
|