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.
@@ -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