jenkins_api_client 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,83 @@
1
+ #
2
+ # Copyright (c) 2012 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 'thor'
24
+ require 'thor/group'
25
+ require 'terminal-table'
26
+
27
+ module JenkinsApi
28
+ module CLI
29
+ class Node < Thor
30
+ include Thor::Actions
31
+
32
+ desc "list", "List all nodes"
33
+ method_option :filter, :aliases => "-f", :desc => "Regular expression to filter jobs"
34
+ def list
35
+ @client = Helper.setup(parent_options)
36
+ if options[:filter]
37
+ puts @client.node.list(options[:filter])
38
+ else
39
+ puts @client.node.list
40
+ end
41
+ end
42
+
43
+ desc "print_general_attributes", "Prints general attributes of nodes"
44
+ def print_general_attributes
45
+ @client = Helper.setup(parent_options)
46
+ general_attributes = Client::Node::GENERAL_ATTRIBUTES
47
+ rows = []
48
+ general_attributes.each do |attr|
49
+ rows << [attr, @client.node.method("get_#{attr}").call]
50
+ end
51
+ table = Terminal::Table.new :headings => ['Attribute', 'Value'], :rows => rows
52
+ puts table
53
+ end
54
+
55
+ desc "print_node_attributes NODE", "Prints attributes specific to a node"
56
+ def print_node_attributes(node)
57
+ @client = Helper.setup(parent_options)
58
+ node_attributes = Client::Node::NODE_ATTRIBUTES
59
+ rows = []
60
+ node_attributes.each do |attr|
61
+ rows << [attr, @client.node.method("get_node_#{attr}").call(node)]
62
+ end
63
+ table = Terminal::Table.new :headings => ['Attribute', 'Value'], :rows => rows
64
+ puts "Node: #{node}"
65
+ puts table
66
+ end
67
+
68
+ desc "print_node_properties NODE", "Prints properties of a node"
69
+ def print_node_properties(node)
70
+ @client = Helper.setup(parent_options)
71
+ node_properties = Client::Node::NODE_PROPERTIES
72
+ rows = []
73
+ node_properties.each do |property|
74
+ rows << [property, @client.node.method("is_#{property}?").call(node)]
75
+ end
76
+ table = Terminal::Table.new :headings => ['Property', 'Value'], :rows => rows
77
+ puts "Node: #{node}"
78
+ puts table
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,53 @@
1
+ #
2
+ # Copyright (c) 2012 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 'thor'
24
+ require 'thor/group'
25
+
26
+ module JenkinsApi
27
+ module CLI
28
+ class System < Thor
29
+ include Thor::Actions
30
+
31
+ desc "quietdown", "Puts the Jenkins server in Quiet down mode"
32
+ def quietdown
33
+ @client = Helper.setup(parent_options)
34
+ @client.system.quiet_down
35
+ end
36
+
37
+ desc "cancel_quietdown", "Cancels the Quiet down mode of Jenkins server"
38
+ def cancel_quietdown
39
+ @client = Helper.setup(parent_options)
40
+ @client.system.cancel_quiet_down
41
+ end
42
+
43
+ desc "restart", "Restarts the Jenkins server"
44
+ method_option :force, :type => :boolean, :aliases => "-s", :desc => "Force restart"
45
+ def restart
46
+ @client = Helper.setup(parent_options)
47
+ force = options[:force] ? true : false
48
+ @client.system.restart(force)
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -31,6 +31,8 @@ require 'base64'
31
31
  require File.expand_path('../version', __FILE__)
32
32
  require File.expand_path('../exceptions', __FILE__)
33
33
  require File.expand_path('../job', __FILE__)
34
+ require File.expand_path('../system', __FILE__)
35
+ require File.expand_path('../node', __FILE__)
34
36
 
35
37
  module JenkinsApi
36
38
  class Client
@@ -61,18 +63,45 @@ module JenkinsApi
61
63
  @password = Base64.decode64(@password_base64).chomp if @password_base64
62
64
  end
63
65
 
64
- # Creates an instance to the Job object by passing a reference to self
66
+ # This method toggles the debug parameter in run time
67
+ #
68
+ def toggle_debug
69
+ @debug = @debug == false ? true : false
70
+ end
71
+
72
+ # Creates an instance to the Job class by passing a reference to self
65
73
  #
66
74
  def job
67
75
  JenkinsApi::Client::Job.new(self)
68
76
  end
69
77
 
78
+ # Creates an instance to the System class by passing a reference to self
79
+ #
80
+ def system
81
+ JenkinsApi::Client::System.new(self)
82
+ end
83
+
84
+ # Creates an instance to the Node class by passing a reference to self
85
+ #
86
+ def node
87
+ JenkinsApi::Client::Node.new(self)
88
+ end
89
+
70
90
  # Returns a string representing the class name
71
91
  #
72
92
  def to_s
73
93
  "#<JenkinsApi::Client>"
74
94
  end
75
95
 
96
+ # Obtains the root of Jenkins server. This function is used to see if
97
+ # Jenkins is running
98
+ def get_root
99
+ http = Net::HTTP.start(@server_ip, @server_port)
100
+ request = Net::HTTP::Get.new("/")
101
+ request.basic_auth @username, @password
102
+ http.request(request)
103
+ end
104
+
76
105
  # Sends a GET request to the Jenkins CI server with the specified URL
77
106
  #
78
107
  # @param [String] url_prefix
@@ -80,6 +109,8 @@ module JenkinsApi
80
109
  def api_get_request(url_prefix, tree = nil)
81
110
  http = Net::HTTP.start(@server_ip, @server_port)
82
111
  request = Net::HTTP::Get.new("#{url_prefix}/api/json")
112
+ puts "[DEBUG] debug value is: #{@debug}"
113
+ puts "[INFO] GET #{url_prefix}/api/json" if @debug
83
114
  request = Net::HTTP::Get.new("#{url_prefix}/api/json?#{tree}") if tree
84
115
  request.basic_auth @username, @password
85
116
  response = http.request(request)
@@ -104,6 +135,7 @@ module JenkinsApi
104
135
  def api_post_request(url_prefix)
105
136
  http = Net::HTTP.start(@server_ip, @server_port)
106
137
  request = Net::HTTP::Post.new("#{url_prefix}")
138
+ puts "[INFO] PUT #{url_prefix}" if @debug
107
139
  request.basic_auth @username, @password
108
140
  response = http.request(request)
109
141
  case response.code.to_i
@@ -125,6 +157,7 @@ module JenkinsApi
125
157
  def get_config(url_prefix)
126
158
  http = Net::HTTP.start(@server_ip, @server_port)
127
159
  request = Net::HTTP::Get.new("#{url_prefix}/config.xml")
160
+ puts "[INFO] GET #{url_prefix}/config.xml" if @debug
128
161
  request.basic_auth @username, @password
129
162
  response = http.request(request)
130
163
  response.body
@@ -138,6 +171,7 @@ module JenkinsApi
138
171
  def post_config(url_prefix, xml)
139
172
  http = Net::HTTP.start(@server_ip, @server_port)
140
173
  request = Net::HTTP::Post.new("#{url_prefix}")
174
+ puts "[INFO] PUT #{url_prefix}" if @debug
141
175
  request.basic_auth @username, @password
142
176
  request.body = xml
143
177
  request.content_type = 'application/xml'
@@ -124,15 +124,12 @@ module JenkinsApi
124
124
  response_json["builds"]
125
125
  end
126
126
 
127
- # Obtain the current build status of the job
128
- # By defaule Jenkins returns the color of the job status icon
129
- # This function translates the color into a meaningful status
127
+ # This method maps the color to status of a job
130
128
  #
131
- # @param [String] job_name
129
+ # @param [String] color color given by the API for a job
132
130
  #
133
- def get_current_build_status(job_name)
134
- response_json = @client.api_get_request("/job/#{job_name}")
135
- case response_json["color"]
131
+ def color_to_status(color)
132
+ case color
136
133
  when "blue"
137
134
  "success"
138
135
  when "red"
@@ -145,16 +142,29 @@ module JenkinsApi
145
142
  "not run"
146
143
  when "aborted"
147
144
  "aborted"
145
+ else
146
+ "invalid"
148
147
  end
149
148
  end
150
149
 
150
+ # Obtain the current build status of the job
151
+ # By defaule Jenkins returns the color of the job status icon
152
+ # This function translates the color into a meaningful status
153
+ #
154
+ # @param [String] job_name
155
+ #
156
+ def get_current_build_status(job_name)
157
+ response_json = @client.api_get_request("/job/#{job_name}")
158
+ color_to_status(response_json["color"])
159
+ end
160
+
151
161
  # This functions lists all jobs that are currently running on the Jenkins CI server
152
162
  #
153
163
  def list_running
154
164
  xml_response = @client.api_get_request("", "tree=jobs[name,color]")
155
165
  running_jobs = []
156
166
  xml_response["jobs"].each { |job|
157
- running_jobs << job["name"] if job["color"] =~ /anime/
167
+ running_jobs << job["name"] if color_to_status(job["color"]) == "running"
158
168
  }
159
169
  running_jobs
160
170
  end
@@ -292,6 +302,17 @@ module JenkinsApi
292
302
  post_config(job_name, xml_modified)
293
303
  end
294
304
 
305
+ # Unchain any existing chain between given job names
306
+ #
307
+ # @param [Array] job_names Array of job names to be unchained
308
+ #
309
+ def unchain(job_names)
310
+ job_names.each { |job|
311
+ puts "[INFO] Removing downstream projects for <#{job}>" if @client.debug
312
+ @client.job.remove_downstream_projects(job)
313
+ }
314
+ end
315
+
295
316
  # Chain the jobs given based on specified criteria
296
317
  #
297
318
  # @param [Array] job_names Array of job names to be chained
@@ -301,24 +322,19 @@ module JenkinsApi
301
322
  #
302
323
  def chain(job_names, threshold, criteria, parallel = 1)
303
324
  raise "Parallel jobs should be at least 1" if parallel < 1
304
-
305
- job_names.each { |job|
306
- puts "INFO: Removing downstream projects for <#{job}>" if @client.debug
307
- @client.job.remove_downstream_projects(job)
308
- }
309
-
325
+ unchain(job_names)
310
326
  filtered_job_names = []
311
327
  if criteria.include?("all") || criteria.empty?
312
328
  filtered_job_names = job_names
313
329
  else
314
- puts "INFO: Criteria is specified. Filtering jobs..." if @client.debug
330
+ puts "[INFO] Criteria is specified. Filtering jobs..." if @client.debug
315
331
  job_names.each { |job|
316
332
  filtered_job_names << job if criteria.include?(@client.job.get_current_build_status(job))
317
333
  }
318
334
  end
319
335
  filtered_job_names.each_with_index { |job_name, index|
320
336
  break if index >= (filtered_job_names.length - parallel)
321
- puts "INFO: Adding <#{filtered_job_names[index+1]}> as a downstream project to <#{job_name}> with <#{threshold}> as the threshold" if @client.debug
337
+ puts "[INFO] Adding <#{filtered_job_names[index+1]}> as a downstream project to <#{job_name}> with <#{threshold}> as the threshold" if @client.debug
322
338
  @client.job.add_downstream_projects(job_name, filtered_job_names[index + parallel], threshold, true)
323
339
  }
324
340
  parallel = filtered_job_names.length if parallel > filtered_job_names.length
@@ -0,0 +1,149 @@
1
+ #
2
+ # Copyright (c) 2012 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
+ module JenkinsApi
24
+ class Client
25
+ class Node
26
+
27
+ # General attributes of a node.
28
+ # This allows the following methods to be called on this node object.
29
+ # These methods are defined using define_method and are prefixed
30
+ # with get_.
31
+ #
32
+ # def get_busyExecutors
33
+ # def get_displayName
34
+ # def get_totalExecutors
35
+ #
36
+ GENERAL_ATTRIBUTES = [
37
+ "busyExecutors",
38
+ "displayName",
39
+ "totalExecutors"
40
+ ]
41
+
42
+ # Properties of a node.
43
+ # The following methods are defined to be called on the node object
44
+ # and are prefixed with is_ and end with ? as they return true or false.
45
+ #
46
+ # def is_idle?(node_name)
47
+ # def is_jnlpAgent?(node_name)
48
+ # def is_launchSupported?(node_name)
49
+ # def is_manualLaunchAllowed?(node_name)
50
+ # def is_offline?(node_name)
51
+ # def is_temporarilyOffline?(node_name)
52
+ #
53
+ NODE_PROPERTIES = [
54
+ "idle",
55
+ "jnlpAgent",
56
+ "launchSupported",
57
+ "manualLaunchAllowed",
58
+ "offline",
59
+ "temporarilyOffline"
60
+ ]
61
+
62
+ # Node specific attributes.
63
+ # The following methods are defined using define_method.
64
+ # These methods are prefixed with get_node_.
65
+ #
66
+ # def get_node_numExecutors(node_name)
67
+ # def get_node_icon(node_name)
68
+ # def get_node_displayName(node_name)
69
+ # def get_node_loadStatistics(node_name)
70
+ # def get_node_monitorData(node_name)
71
+ # def get_node_offlineCause(node_name)
72
+ # def get_node_oneOffExecutors(node_name)
73
+ #
74
+ NODE_ATTRIBUTES = [
75
+ "numExecutors",
76
+ "icon",
77
+ "displayName",
78
+ "loadStatistics",
79
+ "monitorData",
80
+ "offlineCause",
81
+ "oneOffExecutors"
82
+ ]
83
+
84
+ # Initializes a new node object
85
+ #
86
+ # @param [Object] client reference to Client
87
+ #
88
+ def initialize(client)
89
+ @client = client
90
+ end
91
+
92
+ def to_s
93
+ "#<JenkinsApi::Client::Node>"
94
+ end
95
+
96
+ # This method lists all nodes
97
+ #
98
+ # @param [String] filter a regex to filter node names
99
+ # @param [Bool] ignorecase whether to be case sensitive or not
100
+ #
101
+ def list(filter = nil, ignorecase = true)
102
+ node_names = []
103
+ response_json = @client.api_get_request("/computer")
104
+ response_json["computer"].each { |computer|
105
+ node_names << computer["displayName"] if computer["displayName"] =~ /#{filter}/i
106
+ }
107
+ node_names
108
+ end
109
+
110
+ # Identifies the index of a node name in the array node nodes
111
+ #
112
+ # @param [String] node_name name of the node
113
+ #
114
+ def index(node_name)
115
+ response_json = @client.api_get_request("/computer")
116
+ response_json["computer"].each_with_index { |computer, index|
117
+ return index if computer["displayName"] == node_name
118
+ }
119
+ end
120
+
121
+ # Defines methods for general node attributes.
122
+ #
123
+ GENERAL_ATTRIBUTES.each do |meth_suffix|
124
+ define_method("get_#{meth_suffix}") do
125
+ response_json = @client.api_get_request("/computer")
126
+ response_json["#{meth_suffix}"]
127
+ end
128
+ end
129
+
130
+ # Defines methods for node properties.
131
+ #
132
+ NODE_PROPERTIES.each do |meth_suffix|
133
+ define_method("is_#{meth_suffix}?") do |node_name|
134
+ response_json = @client.api_get_request("/computer")
135
+ response_json["computer"][index(node_name)]["#{meth_suffix}"] =~ /False/i ? false : true
136
+ end
137
+ end
138
+
139
+ # Defines methods for node specific attributes.
140
+ NODE_ATTRIBUTES.each do |meth_suffix|
141
+ define_method("get_node_#{meth_suffix}") do |node_name|
142
+ response_json = @client.api_get_request("/computer")
143
+ response_json["computer"][index(node_name)]["#{meth_suffix}"]
144
+ end
145
+ end
146
+
147
+ end
148
+ end
149
+ end