jenkins_api_client2 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,353 @@
1
+ #
2
+ # Copyright (c) 2012-2013 Kannan Manickam <arangamani.kannan@gmail.com>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+
23
+ require 'jenkins_api_client2/urihelper'
24
+
25
+ module JenkinsApi
26
+ class Client
27
+ # This class communicates with Jenkins "/computer" API to obtain details
28
+ # about nodes or slaves connected to the Jenkins.
29
+ #
30
+ class Node
31
+ include JenkinsApi::UriHelper
32
+
33
+ # General attributes of a node.
34
+ # This allows the following methods to be called on this node object.
35
+ # These methods are defined using define_method and are prefixed
36
+ # with get_.
37
+ #
38
+ # def get_busyExecutors
39
+ # def get_displayName
40
+ # def get_totalExecutors
41
+ #
42
+ GENERAL_ATTRIBUTES = [
43
+ "busyExecutors",
44
+ "displayName",
45
+ "totalExecutors"
46
+ ].freeze
47
+
48
+ # Properties of a node.
49
+ # The following methods are defined to be called on the node object
50
+ # and are prefixed with is_ and end with ? as they return true or false.
51
+ #
52
+ # def is_idle?(node_name)
53
+ # def is_jnlpAgent?(node_name)
54
+ # def is_launchSupported?(node_name)
55
+ # def is_manualLaunchAllowed?(node_name)
56
+ # def is_offline?(node_name)
57
+ # def is_temporarilyOffline?(node_name)
58
+ #
59
+ NODE_PROPERTIES = [
60
+ "idle",
61
+ "jnlpAgent",
62
+ "launchSupported",
63
+ "manualLaunchAllowed",
64
+ "offline",
65
+ "temporarilyOffline"
66
+ ].freeze
67
+
68
+ # Node specific attributes.
69
+ # The following methods are defined using define_method.
70
+ # These methods are prefixed with get_node_.
71
+ #
72
+ # def get_node_numExecutors(node_name)
73
+ # def get_node_icon(node_name)
74
+ # def get_node_displayName(node_name)
75
+ # def get_node_loadStatistics(node_name)
76
+ # def get_node_monitorData(node_name)
77
+ # def get_node_offlineCause(node_name)
78
+ # def get_node_oneOffExecutors(node_name)
79
+ #
80
+ NODE_ATTRIBUTES = [
81
+ "numExecutors",
82
+ "icon",
83
+ "displayName",
84
+ "loadStatistics",
85
+ "monitorData",
86
+ "offlineCause",
87
+ "oneOffExecutors"
88
+ ].freeze
89
+
90
+ # Initializes a new node object
91
+ #
92
+ # @param client [Client] the client object
93
+ #
94
+ # @return [Node] the node object
95
+ #
96
+ def initialize(client)
97
+ @client = client
98
+ @logger = @client.logger
99
+ end
100
+
101
+ # Gives the string representation of the Object
102
+ #
103
+ def to_s
104
+ "#<JenkinsApi::Client::Node>"
105
+ end
106
+
107
+ # Creates a new node with the specified parameters
108
+ #
109
+ # @param [Hash] params parameters for creating a dumb slave
110
+ # * +:name+ name of the slave
111
+ # * +:description+ description of the new slave
112
+ # * +:executors+ number of executors
113
+ # * +:remote_fs+ Remote FS root
114
+ # * +:labels+ comma separated list of labels
115
+ # * +:mode+ mode of the slave: normal, exclusive
116
+ # * +:slave_host+ Hostname/IP of the slave
117
+ # * +:slave_port+ Slave port
118
+ # * +:private_key_file+ Private key file of master
119
+ # * +:credentials_id+ Id for credential in Jenkins
120
+ #
121
+ # @example Create a Dumb Slave
122
+ # create_dumb_slave(
123
+ # :name => "slave1",
124
+ # :slave_host => "10.10.10.10",
125
+ # :private_key_file => "/root/.ssh/id_rsa",
126
+ # :executors => 10,
127
+ # :labels => "slave, ruby"
128
+ # )
129
+ #
130
+ def create_dumb_slave(params)
131
+ unless params[:name] && params[:slave_host] && params[:private_key_file]
132
+ raise ArgumentError, "Name, slave host, and private key file are" +
133
+ " required for creating a slave."
134
+ end
135
+
136
+ @logger.info "Creating a dumb slave '#{params[:name]}'"
137
+ @logger.debug "Creating a dumb slave with params: #{params.inspect}"
138
+ default_params = {
139
+ :description => "Automatically created through jenkins_api_client2",
140
+ :executors => 2,
141
+ :remote_fs => "/var/jenkins",
142
+ :labels => params[:name],
143
+ :slave_port => 22,
144
+ :mode => "normal",
145
+ :private_key_file => "",
146
+ :credentials_id => ""
147
+ }
148
+
149
+ params = default_params.merge(params)
150
+ labels = params[:labels].split(/\s*,\s*/).join(" ")
151
+ mode = params[:mode].upcase
152
+
153
+ post_params = {
154
+ "name" => params[:name],
155
+ "type" => "hudson.slaves.DumbSlave$DescriptorImpl",
156
+ "json" => {
157
+ "name" => params[:name],
158
+ "nodeDescription" => params[:description],
159
+ "numExecutors" => params[:executors],
160
+ "remoteFS" => params[:remote_fs],
161
+ "labelString" => labels,
162
+ "mode" => mode,
163
+ "type" => "hudson.slaves.DumbSlave$DescriptorImpl",
164
+ "retentionStrategy" => {
165
+ "stapler-class" => "hudson.slaves.RetentionStrategy$Always"
166
+ },
167
+ "nodeProperties" => {
168
+ "stapler-class-bag" => "true"
169
+ },
170
+ "launcher" => {
171
+ "stapler-class" => "hudson.plugins.sshslaves.SSHLauncher",
172
+ "host" => params[:slave_host],
173
+ "port" => params[:slave_port],
174
+ "username" => params[:slave_user],
175
+ "privatekey" => params[:private_key_file],
176
+ "credentialsId" => params[:credentials_id]
177
+ }
178
+ }.to_json
179
+ }
180
+ @logger.debug "Modified params posted to create slave:" +
181
+ " #{post_params.inspect}"
182
+ @client.api_post_request("/computer/doCreateItem", post_params)
183
+ end
184
+
185
+ def create_dump_slave(params)
186
+ @logger.warn '[DEPRECATED] Please use create_dumb_slave instead.'
187
+ create_dumb_slave(params)
188
+ end
189
+
190
+ # Deletes the specified node
191
+ #
192
+ # @param [String] node_name Name of the node to delete
193
+ #
194
+ def delete(node_name)
195
+ @logger.info "Deleting node '#{node_name}'"
196
+ if list.include?(node_name)
197
+ @client.api_post_request("/computer/#{path_encode node_name}/doDelete")
198
+ else
199
+ raise "The specified node '#{node_name}' doesn't exist in Jenkins."
200
+ end
201
+ end
202
+
203
+ # Deletes all slaves from Jenkins. The master will be the only node alive
204
+ # after the exection of this call.
205
+ #
206
+ # @note This method will remove all slaves from Jenkins. Please use with
207
+ # caution.
208
+ #
209
+ def delete_all!
210
+ @logger.info "Deleting all nodes (except master) from jenkins"
211
+ list.each { |node| delete(node) unless node == "master" }
212
+ end
213
+
214
+ # This method returns two lists 1) nodes online 2) nodes offline
215
+ #
216
+ # @param [String] filter a regex to filter node names
217
+ # @param [Bool] ignorecase whether to be case sensitive or not
218
+ #
219
+ def online_offline_lists(filter = nil, ignorecase = true)
220
+ @logger.info "Obtaining nodes from jenkins matching filter '#{filter}'"
221
+ offline_node_names = []
222
+ online_node_names = []
223
+ response_json = @client.api_get_request("/computer")
224
+ response_json["computer"].each do |computer|
225
+ if computer["displayName"] =~ /#{filter}/i
226
+ if computer["offline"] == true
227
+ offline_node_names << computer["displayName"]
228
+ else
229
+ online_node_names << computer["displayName"]
230
+ end
231
+ end
232
+ end
233
+ return online_node_names, offline_node_names
234
+ end
235
+
236
+ # This method lists all nodes
237
+ #
238
+ # @param [String] filter a regex to filter node names
239
+ # @param [Bool] ignorecase whether to be case sensitive or not
240
+ #
241
+ def list(filter = nil, ignorecase = true, slaveonly = false)
242
+ @logger.info "Obtaining nodes from jenkins matching filter '#{filter}'"
243
+ node_names = []
244
+ response_json = @client.api_get_request("/computer")
245
+ response_json["computer"].each do |computer|
246
+ if computer["displayName"] =~ /#{filter}/i
247
+ unless slaveonly && computer["displayName"] == "master"
248
+ node_names << computer["displayName"]
249
+ end
250
+ end
251
+ end
252
+ node_names
253
+ end
254
+
255
+ # Identifies the index of a node name in the array node nodes
256
+ #
257
+ # @param [String] node_name name of the node
258
+ #
259
+ def index(node_name)
260
+ response_json = @client.api_get_request("/computer")
261
+ response_json["computer"].each_with_index do |computer, index|
262
+ return index if computer["displayName"] == node_name
263
+ end
264
+ end
265
+
266
+ # Defines methods for general node attributes.
267
+ #
268
+ GENERAL_ATTRIBUTES.each do |meth_suffix|
269
+ define_method("get_#{meth_suffix}") do
270
+ @logger.info "Obtaining '#{meth_suffix}' attribute from jenkins"
271
+ response_json = @client.api_get_request("/computer", "tree=#{path_encode meth_suffix}[*[*[*]]]")
272
+ response_json["#{meth_suffix}"]
273
+ end
274
+ end
275
+
276
+ # Defines methods for node properties.
277
+ #
278
+ NODE_PROPERTIES.each do |meth_suffix|
279
+ define_method("is_#{meth_suffix}?") do |node_name|
280
+ @logger.info "Obtaining '#{meth_suffix}' property of '#{node_name}'"
281
+ node_name = "(master)" if node_name == "master"
282
+ response_json = @client.api_get_request("/computer/#{path_encode node_name}", "tree=#{path_encode meth_suffix}")
283
+ resp = response_json["#{meth_suffix}"].to_s
284
+ resp =~ /False/i ? false : true
285
+ end
286
+ end
287
+
288
+ # Defines methods for node specific attributes.
289
+ NODE_ATTRIBUTES.each do |meth_suffix|
290
+ define_method("get_node_#{meth_suffix}") do |node_name|
291
+ @logger.info "Obtaining '#{meth_suffix}' attribute of '#{node_name}'"
292
+ node_name = "(master)" if node_name == "master"
293
+ response_json = @client.api_get_request("/computer/#{path_encode node_name}", "tree=#{path_encode meth_suffix}[*[*[*]]]")
294
+ response_json["#{meth_suffix}"]
295
+ end
296
+ end
297
+
298
+ # Changes the mode of a slave node in Jenkins
299
+ #
300
+ # @param [String] node_name name of the node to change mode for
301
+ # @param [String] mode mode to change to
302
+ #
303
+ def change_mode(node_name, mode)
304
+ @logger.info "Changing the mode of '#{node_name}' to '#{mode}'"
305
+ mode = mode.upcase
306
+ xml = get_config(node_name)
307
+ n_xml = Nokogiri::XML(xml)
308
+ desc = n_xml.xpath("//mode").first
309
+ desc.content = "#{mode.upcase}"
310
+ xml_modified = n_xml.to_xml
311
+ post_config(node_name, xml_modified)
312
+ end
313
+
314
+ # Obtains the configuration of node from Jenkins server
315
+ #
316
+ # @param [String] node_name name of the node
317
+ #
318
+ def get_config(node_name)
319
+ @logger.info "Obtaining the config.xml of node '#{node_name}'"
320
+ node_name = "(master)" if node_name == "master"
321
+ @client.get_config("/computer/#{path_encode node_name}")
322
+ end
323
+
324
+ # Posts the given config.xml to the Jenkins node
325
+ #
326
+ # @param [String] node_name name of the node
327
+ # @param [String] xml Config.xml of the node
328
+ #
329
+ def post_config(node_name, xml)
330
+ @logger.info "Posting the config.xml of node '#{node_name}'"
331
+ node_name = "(master)" if node_name == "master"
332
+ @client.post_config("/computer/#{path_encode node_name}/config.xml", xml)
333
+ end
334
+
335
+ # Toggles the temporarily offline state of the Jenkins node
336
+ #
337
+ # @param [String] node_name name of the node
338
+ # @param [String] reason Offline reason why the node is offline
339
+ #
340
+ def toggle_temporarilyOffline(node_name, reason="")
341
+ @logger.info "Toggling the temporarily offline status of of node '#{node_name}' with reason '#{reason}'"
342
+ node_name = "(master)" if node_name == "master"
343
+ previous_state = is_temporarilyOffline?(node_name)
344
+ @client.api_post_request("/computer/#{path_encode node_name}/toggleOffline?offlineMessage=#{path_encode reason}")
345
+ new_state = is_temporarilyOffline?(node_name)
346
+ if new_state == previous_state
347
+ raise "The specified node '#{node_name}' was unable to change offline state."
348
+ end
349
+ new_state
350
+ end
351
+ end
352
+ end
353
+ end