jenkins_api_client 0.2.1 → 0.3.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,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