jenkins_api_client 0.13.0 → 0.14.0

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