lhj-tools 0.1.25 → 0.1.26

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