jenkins_api_client 0.13.0 → 0.14.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.
@@ -57,12 +57,18 @@ module JenkinsApi
57
57
  # Support for backward compatibility
58
58
  JobAlreadyExistsWithName = JobAlreadyExists
59
59
 
60
+ # This exception class handles cases where a view not able to be created
61
+ # because it already exists
62
+ #
60
63
  class ViewAlreadyExists < ApiException
61
64
  def initialize(logger, message = "", log_level = Logger::ERROR)
62
65
  super(logger, message)
63
66
  end
64
67
  end
65
68
 
69
+ # This exception class handles cases where a node not able to be created
70
+ # because it already exists
71
+ #
66
72
  class NodeAlreadyExists < ApiException
67
73
  def initialize(logger, message = "", log_level = Logger::ERROR)
68
74
  super(logger, message)
@@ -78,38 +84,77 @@ module JenkinsApi
78
84
  super(logger, msg, Logger::FATAL)
79
85
  end
80
86
  end
87
+ # Support for backward compatibility
81
88
  UnauthorizedException = Unauthorized
82
89
 
83
90
  # This exception class handles cases where invalid credentials are provided
84
91
  # to connect to the Jenkins.
92
+ # While it is apparently used to indicate expiry of a Crumb, this is not
93
+ # the only cause of a forbidden error... maybe the user just isn't allowed
94
+ # to access the given url. We should treat forbidden as a specific "you
95
+ # are not welcome here"
85
96
  #
86
97
  class Forbidden < ApiException
87
98
  def initialize(logger, message = "", log_level = Logger::ERROR)
88
- msg = "The Crumb was expired or not sent to the server." +
89
- " Perhaps the CSRF protection was not enabled on the server" +
90
- " when the client was initialized. Please re-initialize the" +
91
- " client. #{message}"
99
+ msg = "Access denied. Please ensure that Jenkins is set up to allow" +
100
+ " access to this operation. #{message}"
92
101
  super(logger, msg)
93
102
  end
94
103
  end
95
104
  # Support for backward compatibility
96
105
  ForbiddenException = Forbidden
97
106
 
107
+ # This exception should be thrown specifically when the caller has had
108
+ # a ForbiddenException and has been able to determine that a (valid)
109
+ # crumb was used, and the attempt still failed.
110
+ # This may require an interim attempt to re-acquire the crumb in order
111
+ # to confirm it has not expired.
112
+ #
113
+ # @example A condition where this exception would be raised
114
+ # def operation
115
+ # retried = false
116
+ # begin
117
+ # make_attempt
118
+ # rescue Forbidden => e
119
+ # refresh_crumbs(true)
120
+ # if @crumbs_enabled
121
+ # if !retried
122
+ # retried = true
123
+ # retry
124
+ # else
125
+ # raise ForbiddenWithCrumb.new(@logger, e.message)
126
+ # end
127
+ # else
128
+ # raise
129
+ # end
130
+ # end
131
+ # end
132
+ #
133
+ # @note the 'refresh_crumbs' method will update crumb enablement and the
134
+ # stored crumb if called with 'true'
135
+ #
136
+ class ForbiddenWithCrumb < Forbidden
137
+ def initialize(logger, message = '', log_level = Logger::ERROR)
138
+ msg = "A crumb was used in attempt to access operation. #{message}"
139
+ super(logger, msg)
140
+ end
141
+ end
142
+
98
143
  # This exception class handles cases where a requested page is not found on
99
144
  # the Jenkins API.
100
145
  #
101
146
  class NotFound < ApiException
102
147
  def initialize(logger, message = "", log_level = Logger::ERROR)
103
- msg = "Requested component is not found on the Jenkins CI server." \
104
- if message.empty?
148
+ msg = message.empty? ? "Requested component is not found on the" +
149
+ " Jenkins CI server." : message
105
150
  super(logger, msg)
106
151
  end
107
152
  end
108
153
  # Support for backward compatibility
109
154
  NotFoundException = NotFound
110
155
 
111
- # This exception class handles cases where a requested page is not found on
112
- # the Jenkins API.
156
+ # This exception class handles cases when Crumb Issues did not issue a
157
+ # crumb upon request.
113
158
  #
114
159
  class CrumbNotFound < NotFound
115
160
  def initialize(logger, message = "", log_level = Logger::ERROR)
@@ -117,24 +162,35 @@ module JenkinsApi
117
162
  super(logger, msg)
118
163
  end
119
164
  end
165
+ # Support for backward compatibility
166
+ CrumbNotFoundException = CrumbNotFound
120
167
 
168
+ # This exception class handles cases where the requested job does not exist
169
+ # in Jenkins.
170
+ #
121
171
  class JobNotFound < NotFound
122
172
  def initialize(logger, message = "", log_level = Logger::ERROR)
123
- msg = "The specified job is not found" if message.empty?
173
+ msg = message.empty? ? "The specified job is not found" : message
124
174
  super(logger, msg)
125
175
  end
126
176
  end
127
177
 
178
+ # This exception class handles cases where the requested view does not exist
179
+ # in Jenkins.
180
+ #
128
181
  class ViewNotFound < NotFound
129
182
  def initialize(logger, message = "", log_level = Logger::ERROR)
130
- msg = "The specified view is not found" if message.empty?
183
+ msg = message.empty? ? "The specified view is not found" : message
131
184
  super(logger, msg)
132
185
  end
133
186
  end
134
187
 
188
+ # This exception class handles cases where the requested node does not exist
189
+ # in Jenkins.
190
+ #
135
191
  class NodeNotFound < NotFound
136
192
  def initialize(logger, message = "", log_level = Logger::ERROR)
137
- msg = "The specified node is not found" if message.empty?
193
+ msg = msg.empty? ? "The specified node is not found" : message
138
194
  super(logger, msg)
139
195
  end
140
196
  end
@@ -29,6 +29,10 @@ module JenkinsApi
29
29
 
30
30
  # Initialize the Job object and store the reference to Client object
31
31
  #
32
+ # @param client [Client] the client object
33
+ #
34
+ # @return [Job] the job object
35
+ #
32
36
  def initialize(client)
33
37
  @client = client
34
38
  @logger = @client.logger
@@ -40,53 +44,133 @@ module JenkinsApi
40
44
  "#<JenkinsApi::Client::Job>"
41
45
  end
42
46
 
47
+ # Create or Update a job with the name specified and the xml given
48
+ #
49
+ # @param job_name [String] the name of the job
50
+ # @param xml [String] the xml configuration of the job
51
+ #
52
+ # @see #create
53
+ # @see #update
54
+ #
55
+ # @return [String] the HTTP status code from the POST request
56
+ #
57
+ def create_or_update(job_name, xml)
58
+ if exists?(name)
59
+ update(job_name, xml)
60
+ else
61
+ create(job_name, xml)
62
+ end
63
+ end
64
+
43
65
  # Create a job with the name specified and the xml given
44
66
  #
45
- # @param [String] job_name
46
- # @param [XML] xml
67
+ # @param job_name [String] the name of the job
68
+ # @param xml [String] the xml configuration of the job
69
+ #
70
+ # @see #create_or_update
71
+ # @see #update
72
+ #
73
+ # @return [String] the HTTP status code from the POST request
47
74
  #
48
75
  def create(job_name, xml)
49
76
  @logger.info "Creating job '#{job_name}'"
50
77
  @client.post_config("/createItem?name=#{job_name}", xml)
51
78
  end
52
79
 
53
- # Create a job with params given as a hash instead of the xml
54
- # This gives some flexibility for creating simple jobs so the user
55
- # doesn't have to learn about handling xml.
56
- #
57
- # @param [Hash] params
58
- # * +:name+ name of the job
59
- # * +:keep_dependencies+ true or false
60
- # * +:block_build_when_downstream_building+ true or false
61
- # * +:block_build_when_upstream_building+ true or false
62
- # * +:concurrent_build+ true or false
63
- # * +:scm_provider+ type of source control. Supported: Git, SVN, and CVS
64
- # * +:scm_url+ remote url for scm
65
- # * +:scm_module+ Module to download. Only for CVS.
66
- # * +:scm_branch+ branch to use in scm. Uses master by default
67
- # * +:scm_tag+ tag to download from scm. Only for CVS.
68
- # * +:scm_use_head_if_tag_not_found+ Only for CVS.
69
- # * +:timer+ timer for running builds periodically.
70
- # * +:shell_command+ command to execute in the shell
71
- # * +:notification_email+ email for sending notification
72
- # * +:skype_targets+ skype targets for sending notifications to. Use *
73
- # to specify group chats. Use space to separate multiple targets.
74
- # Example: testuser *testgroup.
75
- # * +:skype_strategy+ skype strategy to be used for sending
76
- # notifications. Valid values: all, failure, failure_and_fixed,
77
- # change. Default: change.
78
- # * +:skype_notify_on_build_start+ Default: false
79
- # * +:skype_notify_suspects+ Default: false
80
- # * +:skype_notify_culprits+ Default: false
81
- # * +:skype_notify_fixers+ Default: false
82
- # * +:skype_notify_upstream_committers+ Default: false
83
- # * +:skype_message+ what should be sent as notification message. Valid:
84
- # just_summary, summary_and_scm_changes,
85
- # summary_and_build_parameters, summary_scm_changes_and_failed_tests.
86
- # Default: summary_and_scm_changes
87
- # * +:child_projects+ projects to add as downstream projects
88
- # * +:child_threshold+ threshold for child projects.
89
- # success, failure, or unstable. Default: failure.
80
+ # Update a job with the name specified and the xml given
81
+ #
82
+ # @param job_name [String] the name of the job
83
+ # @param xml [String] the xml configuration of the job
84
+ #
85
+ # @see #create_or_update
86
+ # @see #create
87
+ #
88
+ # @return [String] the HTTP status code from the POST request
89
+ #
90
+ def update(job_name, xml)
91
+ @logger.info "Updating job '#{job_name}'"
92
+ post_config(job_name, xml)
93
+ end
94
+
95
+ # Create or Update a job with params given as a hash instead of the xml
96
+ # This gives some flexibility for creating/updating simple jobs so the
97
+ # user doesn't have to learn about handling xml.
98
+ #
99
+ # @param params [Hash] parameters to create a freestyle project
100
+ #
101
+ # @option params [String] :name
102
+ # the name of the job
103
+ # @option params [Boolean] :keep_dependencies (false)
104
+ # whether to keep the dependencies or not
105
+ # @option params [Boolean] :block_build_when_downstream_building (false)
106
+ # whether to block build when the downstream project is building
107
+ # @option params [Boolean] :block_build_when_upstream_building (false)
108
+ # whether to block build when the upstream project is building
109
+ # @option params [Boolean] :concurrent_build (false)
110
+ # whether to allow concurrent execution of builds
111
+ # @option params [String] :scm_provider
112
+ # the type of source control. Supported providers: git, svn, and cvs
113
+ # @option params [String] :scm_url
114
+ # the remote url for the selected scm provider
115
+ # @option params [String] :scm_module
116
+ # the module to download. Only for use with "cvs" scm provider
117
+ # @option params [String] :scm_branch (master)
118
+ # the branch to use in scm.
119
+ # @option params [String] :scm_tag
120
+ # the tag to download from scm. Only for use with "cvs" scm provider
121
+ # @option params [Boolean] :scm_use_head_if_tag_not_found
122
+ # whether to use head if specified tag is not found. Only for "cvs"
123
+ # @option params [String] :timer
124
+ # the timer for running builds periodically
125
+ # @option params [String] :shell_command
126
+ # the command to execute in the shell
127
+ # @option params [String] :notification_email
128
+ # the email for sending notification
129
+ # @option params [String] :skype_targets
130
+ # the skype targets for sending notifications to. Use * to specify
131
+ # group chats. Use space to separate multiple targets. Note that this
132
+ # option requires the "skype" plugin to be installed in jenkins.
133
+ # Example: testuser *testgroup
134
+ # @option params [String] :skype_strategy (change)
135
+ # the skype strategy to be used for sending notifications.
136
+ # Valid values: all, failure, failure_and_fixed, change.
137
+ # @option params [Boolean] :skype_notify_on_build_start (false)
138
+ # whether to notify skype targets on build start
139
+ # @option params [Boolean] :skype_notify_suspects (false)
140
+ # whether to notify suspects on skype
141
+ # @option params [Boolean] :skype_notify_culprits (false)
142
+ # whether to notify culprits on skype
143
+ # @option params [Boolean] :skype_notify_fixers (false)
144
+ # whether to notify fixers on skype
145
+ # @option params [Boolean] :skype_notify_upstream_committers (false)
146
+ # whether to notify upstream committers on skype
147
+ # @option params [String] :skype_message (summary_and_scm_changes)
148
+ # the information to be sent as notification message. Valid:
149
+ # just_summary, summary_and_scm_changes,
150
+ # summary_and_build_parameters, summary_scm_changes_and_failed_tests.
151
+ # @option params [String] :child_projects
152
+ # the projects to add as downstream projects
153
+ # @option params [String] :child_threshold (failure)
154
+ # the threshold for child projects. Valid options: success, failure,
155
+ # or unstable.
156
+ #
157
+ # @see #create_freestyle
158
+ # @see #update_freestyle
159
+ #
160
+ # @return [String] the HTTP status code from the POST request
161
+ #
162
+ def create_or_update_freestyle(params)
163
+ if exists?(params[:name])
164
+ update_freestyle(params)
165
+ else
166
+ create_freestyle(params)
167
+ end
168
+ end
169
+
170
+ # Create a freestyle project by accepting a Hash of parameters. For the
171
+ # parameter description see #create_of_update_freestyle
172
+ #
173
+ # @param params [Hash] the parameters for creating a job
90
174
  #
91
175
  # @example Create a Freestype Project
92
176
  # create_freestyle(
@@ -99,23 +183,56 @@ module JenkinsApi
99
183
  # :shell_command => "bundle install\n rake func_tests"
100
184
  # )
101
185
  #
186
+ # @see #create_or_update_freestyle
187
+ # @see #create
188
+ # @see #update_freestyle
189
+ #
190
+ # @return [String] the HTTP status code from the POST request
191
+ #
102
192
  def create_freestyle(params)
193
+ xml = build_freestyle_config(params)
194
+ create(params[:name], xml)
195
+ end
196
+
197
+ # Update a job with params given as a hash instead of the xml. For the
198
+ # parameter description see #create_or_update_freestyle
199
+ #
200
+ # @param params [Hash]
201
+ #
202
+ # @see #create_or_update_freestyle
203
+ # @see #update
204
+ # @see #create_freestyle
205
+ #
206
+ # @return [String] the HTTP status code from the POST request
207
+ #
208
+ def update_freestyle(params)
209
+ xml = build_freestyle_config(params)
210
+ update(params[:name], xml)
211
+ end
212
+
213
+ # Builds the XML configuration based on the parameters passed as a Hash
214
+ #
215
+ # @param params [Hash] the parameters for building XML configuration
216
+ #
217
+ # @return [String] the generated XML configuration of the project
218
+ #
219
+ def build_freestyle_config(params)
103
220
  # Supported SCM providers
104
221
  supported_scm = ["git", "subversion", "cvs"]
105
222
 
106
223
  # Set default values for params that are not specified.
107
224
  raise ArgumentError, "Job name must be specified" \
108
225
  unless params.is_a?(Hash) && params[:name]
109
- if params[:keep_dependencies].nil?
110
- params[:keep_dependencies] = false
111
- end
112
- if params[:block_build_when_downstream_building].nil?
113
- params[:block_build_when_downstream_building] = false
114
- end
115
- if params[:block_build_when_upstream_building].nil?
116
- params[:block_build_when_upstream_building] = false
226
+
227
+ [
228
+ :keep_dependencies,
229
+ :block_build_when_downstream_building,
230
+ :block_build_when_upstream_building,
231
+ :concurrent_build
232
+ ].each do |param|
233
+ params[param] = false if params[param].nil?
117
234
  end
118
- params[:concurrent_build] = false if params[:concurrent_build].nil?
235
+
119
236
  if params[:notification_email]
120
237
  if params[:notification_email_for_every_unstable].nil?
121
238
  params[:notification_email_for_every_unstable] = false
@@ -126,30 +243,27 @@ module JenkinsApi
126
243
  end
127
244
 
128
245
  # SCM configurations and Error handling.
129
- unless supported_scm.include?(params[:scm_provider]) ||
130
- params[:scm_provider].nil?
131
- raise "SCM #{params[:scm_provider]} is currently not supported"
132
- end
133
- if params[:scm_url].nil? && !params[:scm_provider].nil?
134
- raise 'SCM URL must be specified'
135
- end
136
- if params[:scm_branch].nil? && !params[:scm_provider].nil?
137
- params[:scm_branch] = "master"
138
- end
139
- if params[:scm_use_head_if_tag_not_found].nil?
140
- params[:scm_use_head_if_tag_not_found] = false
246
+ unless params[:scm_provider].nil?
247
+ unless supported_scm.include?(params[:scm_provider])
248
+ raise "SCM #{params[:scm_provider]} is currently not supported"
249
+ end
250
+ raise "SCM URL must be specified" if params[:scm_url].nil?
251
+ params[:scm_branch] = "master" if params[:scm_branch].nil?
252
+ if params[:scm_use_head_if_tag_not_found].nil?
253
+ params[:scm_use_head_if_tag_not_found] = false
254
+ end
141
255
  end
142
256
 
143
257
  # Child projects configuration and Error handling
144
258
  if params[:child_threshold].nil? && !params[:child_projects].nil?
145
- params[:child_threshold] = 'failure'
259
+ params[:child_threshold] = "failure"
146
260
  end
147
261
 
148
262
  @logger.debug "Creating a freestyle job with params: #{params.inspect}"
149
263
 
150
264
  # Build the Job xml file based on the parameters given
151
- builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') { |xml|
152
- xml.project {
265
+ builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
266
+ xml.project do
153
267
  xml.actions
154
268
  xml.description
155
269
  xml.keepDependencies "#{params[:keep_dependencies]}"
@@ -180,45 +294,46 @@ module JenkinsApi
180
294
  xml.blockBuildWhenUpstreamBuilding(
181
295
  "#{params[:block_build_when_upstream_building]}")
182
296
  if params[:timer]
183
- xml.triggers.vector {
184
- xml.send("hudson.triggers.TimerTrigger") {
297
+ xml.triggers.vector do
298
+ xml.send("hudson.triggers.TimerTrigger") do
185
299
  xml.spec params[:timer]
186
- }
187
- }
300
+ end
301
+ end
188
302
  else
189
303
  xml.triggers.vector
190
304
  end
191
305
  xml.concurrentBuild "#{params[:concurrent_build]}"
192
306
  # Shell command stuff
193
- xml.builders {
307
+ xml.builders do
194
308
  if params[:shell_command]
195
- xml.send("hudson.tasks.Shell") {
309
+ xml.send("hudson.tasks.Shell") do
196
310
  xml.command "#{params[:shell_command]}"
197
- }
311
+ end
198
312
  end
199
- }
313
+ end
200
314
  # Adding Downstream projects
201
- xml.publishers {
315
+ xml.publishers do
202
316
  # Build portion of XML that adds child projects
203
317
  child_projects(params, xml) if params[:child_projects]
204
318
  # Build portion of XML that adds email notification
205
319
  notification_email(params, xml) if params[:notification_email]
206
320
  # Build portion of XML that adds skype notification
207
321
  skype_notification(params, xml) if params[:skype_targets]
208
- }
322
+ end
209
323
  xml.buildWrappers
210
- }
211
- }
212
- create(params[:name], builder.to_xml)
324
+ end
325
+ end
326
+ builder.to_xml
213
327
  end
214
328
 
329
+
215
330
  # Adding email notification to a job
216
331
  #
217
332
  # @param [Hash] params parameters to add email notification
218
333
  #
219
334
  # @option params [String] :name Name of the job
220
335
  # @option params [String] :notification_email Email address to send
221
- # @option params [TrueClass|FalseClass] :notification_email_for_every_unstable
336
+ # @option params [Boolean] :notification_email_for_every_unstable
222
337
  # Send email notification email for every unstable build
223
338
  #
224
339
  def add_email_notification(params)
@@ -229,9 +344,9 @@ module JenkinsApi
229
344
  xml = get_config(params[:name])
230
345
  n_xml = Nokogiri::XML(xml)
231
346
  if n_xml.xpath("//hudson.tasks.Mailer").empty?
232
- p_xml = Nokogiri::XML::Builder.new(:encoding => "UTF-8") { |xml|
233
- notification_email(params, xml)
234
- }
347
+ p_xml = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |b_xml|
348
+ notification_email(params, b_xml)
349
+ end
235
350
  email_xml = Nokogiri::XML(p_xml.to_xml).xpath(
236
351
  "//hudson.tasks.Mailer"
237
352
  ).first
@@ -267,9 +382,9 @@ module JenkinsApi
267
382
  xml = get_config(params[:name])
268
383
  n_xml = Nokogiri::XML(xml)
269
384
  if n_xml.xpath("//hudson.plugins.skype.im.transport.SkypePublisher").empty?
270
- p_xml = Nokogiri::XML::Builder.new(:encoding => "UTF-8") { |xml|
271
- skype_notification(params, xml)
272
- }
385
+ p_xml = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |b_xml|
386
+ skype_notification(params, b_xml)
387
+ end
273
388
  skype_xml = Nokogiri::XML(p_xml.to_xml).xpath(
274
389
  "//hudson.plugins.skype.im.transport.SkypePublisher"
275
390
  ).first
@@ -371,11 +486,11 @@ module JenkinsApi
371
486
  # @param [String] mode Mode of text output. 'text' or 'html'
372
487
  #
373
488
  # @return [Hash] response
374
- # * +output+ Console output of the job
375
- # * +size+ Size of the text. This can be used as 'start' for the
376
- # next call to get progressive output
377
- # * +more+ More data available for the job. 'true' if available
378
- # and nil otherwise
489
+ # * +output+ console output of the job
490
+ # * +size+ size of the text. This can be used as 'start' for the
491
+ # next call to get progressive output
492
+ # * +more+ more data available for the job. 'true' if available
493
+ # and nil otherwise
379
494
  #
380
495
  def get_console_output(job_name, build_num = 0, start = 0, mode = 'text')
381
496
  build_num = get_current_build_number(job_name) if build_num == 0
@@ -406,10 +521,8 @@ module JenkinsApi
406
521
  # List all jobs on the Jenkins CI server
407
522
  #
408
523
  def list_all
409
- response_json = @client.api_get_request("")
410
- jobs = []
411
- response_json["jobs"].each { |job| jobs << job["name"] }
412
- jobs.sort!
524
+ response_json = @client.api_get_request("", "tree=jobs[name]")["jobs"]
525
+ response_json.map { |job| job["name"] }.sort
413
526
  end
414
527
 
415
528
  # Checks if the given job exists in Jenkins
@@ -553,7 +666,7 @@ module JenkinsApi
553
666
  #
554
667
  # @param [String] job_name
555
668
  #
556
- # @return [Number] build_unumber current build number of the given job
669
+ # @return [Integer] current build number of the given job
557
670
  #
558
671
  def get_current_build_number(job_name)
559
672
  @logger.info "Obtaining the current build number of '#{job_name}'"
@@ -612,6 +725,17 @@ module JenkinsApi
612
725
  end
613
726
  end
614
727
 
728
+ # Programatically schedule SCM polling for the specified job
729
+ #
730
+ # @param job_name [String] the name of the job
731
+ #
732
+ # @return [String] the response code from the HTTP post request
733
+ #
734
+ def poll(job_name)
735
+ @logger.info "Polling SCM changes for job '#{job_name}'"
736
+ @client.api_post_request("/job/#{job_name}/polling")
737
+ end
738
+
615
739
  # Enable a job given the name of the job
616
740
  #
617
741
  # @param [String] job_name