hudson-remote-api 0.1.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.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ hudson_settings.yml
data/README ADDED
@@ -0,0 +1,20 @@
1
+ hudson-remote-api is ruby library to talk to Hudson's xml remote access api
2
+
3
+ Usage:
4
+
5
+ require 'hudson-remote-api'
6
+
7
+ # Configuration
8
+ Hudson[:host] = 'localhost'
9
+ Hudson[:user] = 'hudson'
10
+ Hudson[:password] = 'password'
11
+
12
+ # List all Hudson jobs
13
+ Hudson::Job.list
14
+
15
+ # List all active Hudson jobs
16
+ Hudson::Job.list_active
17
+
18
+ # print the last build number of a job
19
+ j = Hudson::Job.new('jobname')
20
+ puts j.last_build
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gemspec|
7
+ gemspec.name = "hudson-remote-api"
8
+ gemspec.summary = "Connect to Hudson's remote web API"
9
+ gemspec.description = "Connect to Hudson's remote web API"
10
+ gemspec.email = "Druwerd@gmail.com"
11
+ gemspec.homepage = "http://github.com/Druwerd/hudson-remote-api"
12
+ gemspec.authors = ["Dru Ibarra"]
13
+ gemspec.rubyforge_project = gemspec.name
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install jeweler -s http://gemcutter.org"
18
+ end
19
+
20
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,47 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{hudson-remote-api}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Dru Ibarra"]
12
+ s.date = %q{2010-09-01}
13
+ s.description = %q{Connect to Hudson's remote web API}
14
+ s.email = %q{Druwerd@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "README",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "hudson-remote-api.gemspec",
24
+ "lib/hudson-remote-api.rb",
25
+ "lib/hudson-remote-api/build.rb",
26
+ "lib/hudson-remote-api/build_queue.rb",
27
+ "lib/hudson-remote-api/errors.rb",
28
+ "lib/hudson-remote-api/job.rb"
29
+ ]
30
+ s.homepage = %q{http://github.com/Druwerd/hudson-remote-api}
31
+ s.rdoc_options = ["--charset=UTF-8"]
32
+ s.require_paths = ["lib"]
33
+ s.rubyforge_project = %q{hudson-remote-api}
34
+ s.rubygems_version = %q{1.3.7}
35
+ s.summary = %q{Connect to Hudson's remote web API}
36
+
37
+ if s.respond_to? :specification_version then
38
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
+ else
43
+ end
44
+ else
45
+ end
46
+ end
47
+
@@ -0,0 +1,72 @@
1
+ # This set of classes provides a Ruby interface to Hudson's web xml API
2
+ #
3
+ # Author:: Dru Ibarra
4
+
5
+ require 'net/http'
6
+ require 'rexml/document'
7
+ require 'cgi'
8
+ require 'yaml'
9
+ require 'zlib'
10
+
11
+ module Hudson
12
+ @@settings = {:host => 'localhost', :port => 80, :user => nil, :password => nil}
13
+
14
+ def self.[](param)
15
+ return @@settings[param]
16
+ end
17
+
18
+ def self.[]=(param,value)
19
+ @@settings[param]=value
20
+ end
21
+
22
+ def self.settings=(settings)
23
+ @@settings = settings
24
+ end
25
+
26
+ HUDSON_URL_ROOT = ""
27
+ # Base class for all Hudson objects
28
+ class HudsonObject
29
+
30
+ def self.get_xml(path)
31
+ request = Net::HTTP::Get.new(path)
32
+ request.basic_auth(Hudson[:user], Hudson[:password]) if Hudson[:user] and Hudson[:password]
33
+ request['Content-Type'] = "text/xml"
34
+ response = Net::HTTP.start(Hudson[:host], Hudson[:port]){|http| http.request(request)}
35
+
36
+ if response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPRedirection)
37
+ encoding = response.get_fields("Content-Encoding")
38
+ if encoding and encoding.include?("gzip")
39
+ return Zlib::GzipReader.new(StringIO.new(response.body)).read
40
+ else
41
+ return response.body
42
+ end
43
+ else
44
+ puts response
45
+ raise APIError, "Error retrieving #{path}"
46
+ end
47
+ end
48
+
49
+ def get_xml(path)
50
+ self.class.get_xml(path)
51
+ end
52
+
53
+ def send_post_request(path, data={})
54
+ request = Net::HTTP::Post.new(path)
55
+ request.basic_auth(Hudson[:user], Hudson[:password]) if Hudson[:user] and Hudson[:password]
56
+ request.set_form_data(data)
57
+ #puts request.to_yaml
58
+ Net::HTTP.new(Hudson[:host], Hudson[:port]).start{|http| http.request(request)}
59
+ end
60
+
61
+ def send_xml_post_request(path, xml)
62
+ request = Net::HTTP::Post.new(path)
63
+ request.basic_auth(Hudson[:user], Hudson[:password]) if Hudson[:user] and Hudson[:password]
64
+ request.body = xml
65
+ #puts request.body
66
+ #puts request.to_yaml
67
+ Net::HTTP.new(Hudson[:host], Hudson[:port]).start{|http| http.request(request)}
68
+ end
69
+ end
70
+ end
71
+
72
+ Dir[File.dirname(__FILE__) + '/hudson-remote-api/*.rb'].each {|file| require file }
@@ -0,0 +1,29 @@
1
+ require 'hudson-remote-api'
2
+ module Hudson
3
+ class Build < HudsonObject
4
+ attr_reader :number, :job, :revisions, :result
5
+
6
+ def initialize(job, build_number=nil)
7
+ @job = Job.new(job) if job.kind_of?(String)
8
+ @job = job if job.kind_of?(Hudson::Job)
9
+ if build_number
10
+ @number = build_number
11
+ else
12
+ @number = @job.last_build
13
+ end
14
+ @revisions = {}
15
+ load_build_info
16
+ end
17
+
18
+ private
19
+ def load_build_info
20
+ path = "#{HUDSON_URL_ROOT}/job/#{@job.name}/#{@number}/api/xml"
21
+ build_info_xml = get_xml(path)
22
+ build_info_doc = REXML::Document.new(build_info_xml)
23
+
24
+ if !build_info_doc.elements["/freeStyleBuild/changeSet"].nil?
25
+ build_info_doc.elements.each("/freeStyleBuild/changeSet/revision"){|e| @revisions[e.elements["module"].text] = e.elements["revision"].text }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ require 'hudson-remote-api'
2
+ module Hudson
3
+ # This class provides an interface to Hudson's build queue
4
+ class BuildQueue < HudsonObject
5
+ # List the jobs in the queue
6
+ def self.list()
7
+ path = "#{HUDSON_URL_ROOT}/queue/api/xml"
8
+ xml = get_xml(path)
9
+ queue = []
10
+ queue_doc = REXML::Document.new(xml)
11
+ return queue if queue_doc.elements["/queue/item"].nil?
12
+ queue_doc.each_element("/queue/item/task") do |job|
13
+ queue << job.elements["name"].text
14
+ end
15
+ queue
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Hudson
2
+ class APIError < StandardError; end
3
+ end
@@ -0,0 +1,187 @@
1
+ require 'hudson-remote-api'
2
+ module Hudson
3
+ # This class provides an interface to Hudson jobs
4
+ class Job < HudsonObject
5
+
6
+ attr_accessor :name, :config, :repository_url, :repository_urls, :repository_browser_location, :description
7
+ attr_reader :color, :last_build, :last_completed_build, :last_failed_build, :last_stable_build, :last_successful_build, :last_unsuccessful_build, :next_build_number
8
+
9
+ # List all Hudson jobs
10
+ def self.list()
11
+ path = "#{HUDSON_URL_ROOT}/api/xml"
12
+ xml = get_xml(path)
13
+
14
+ jobs = []
15
+ jobs_doc = REXML::Document.new(xml)
16
+ jobs_doc.each_element("hudson/job") do |job|
17
+ jobs << job.elements["name"].text
18
+ end
19
+ jobs
20
+ end
21
+
22
+ # List all jobs in active execution
23
+ def self.list_active
24
+ path = "#{HUDSON_URL_ROOT}/api/xml"
25
+ xml = get_xml(path)
26
+
27
+ active_jobs = []
28
+ jobs_doc = REXML::Document.new(xml)
29
+ jobs_doc.each_element("hudson/job") do |job|
30
+ if job.elements["color"].text.include?("anime")
31
+ active_jobs << job.elements["name"].text
32
+ end
33
+ end
34
+ active_jobs
35
+ end
36
+
37
+ def initialize(name)
38
+ @name = name
39
+ load_config
40
+ load_info
41
+ end
42
+
43
+ # Load data from Hudson's Job configuration settings into class variables
44
+ def load_config()
45
+ path = "#{HUDSON_URL_ROOT}/job/#{@name}/config.xml"
46
+ @config = get_xml(path)
47
+ @config_doc = REXML::Document.new(@config)
48
+
49
+ @config_doc = REXML::Document.new(@config)
50
+ if !@config_doc.elements["/project/scm/locations/hudson.scm.SubversionSCM_-ModuleLocation/remote"].nil?
51
+ @repository_url = @config_doc.elements["/project/scm/locations/hudson.scm.SubversionSCM_-ModuleLocation/remote"].text || ""
52
+ end
53
+ @repository_urls = []
54
+ if !@config_doc.elements["/project/scm/locations"].nil?
55
+ @config_doc.elements.each("/project/scm/locations/hudson.scm.SubversionSCM_-ModuleLocation"){|e| @repository_urls << e.elements["remote"].text }
56
+ end
57
+ if !@config_doc.elements["/project/scm/browser/location"].nil?
58
+ @repository_browser_location = @config_doc.elements["/project/scm/browser/location"].text
59
+ end
60
+ @description = @config_doc.elements["/project/description"].text || ""
61
+ end
62
+
63
+ def load_info()
64
+ path = "#{HUDSON_URL_ROOT}/job/#{@name}/api/xml"
65
+ @info = get_xml(path)
66
+ @info_doc = REXML::Document.new(@info)
67
+
68
+ #FIXME: make sure it's really a freeStyleProject
69
+ @color = @info_doc.elements["/freeStyleProject/color"].text
70
+ @last_build = @info_doc.elements["/freeStyleProject/lastBuild/number"].text
71
+ @last_completed_build = @info_doc.elements["/freeStyleProject/lastCompletedBuild/number"].text
72
+ @last_failed_build = @info_doc.elements["/freeStyleProject/lastFailedBuild/number"].text if @info_doc.elements["/freeStyleProject/lastFailedBuild/number"]
73
+ @last_stable_build = @info_doc.elements["/freeStyleProject/lastStableBuild/number"].text if @info_doc.elements["/freeStyleProject/lastStableBuild/number"]
74
+ @last_successful_build = @info_doc.elements["/freeStyleProject/lastSuccessfulBuild/number"].text if @info_doc.elements["/freeStyleProject/lastSuccessfulBuild/number"]
75
+ @last_unsuccessful_build = @info_doc.elements["/freeStyleProject/lastUnsuccessfulBuild/number"].text if @info_doc.elements["/freeStyleProject/lastUnsuccessfulBuild/number"]
76
+ @next_build_number = @info_doc.elements["/freeStyleProject/nextBuildNumber"].text
77
+ end
78
+
79
+ def active?
80
+ Job.list_active.include?(@name)
81
+ end
82
+
83
+ def wait_for_build_to_finish(poll_freq=10)
84
+ loop do
85
+ puts "waiting for all #{@name} builds to finish"
86
+ sleep poll_freq # wait
87
+ break if !active? and !BuildQueue.list.include?(@name)
88
+ end
89
+ end
90
+
91
+ # Create a new job on Hudson server based on the current job object
92
+ def copy(new_job=nil)
93
+ new_job = "copy_of_#{@name}" if new_job.nil?
94
+ path = "#{HUDSON_URL_ROOT}/createItem"
95
+
96
+ response = send_post_request(path, {:name=>new_job, :mode=>"copy", :from=>@name})
97
+ raise(APIError, "Error copying job #{@name}: #{response.body}") if response.class != Net::HTTPFound
98
+ Job.new(new_job)
99
+ end
100
+
101
+ # Update the job configuration on Hudson server
102
+ def update(config=nil)
103
+ @config = config if !config.nil?
104
+ path = "#{HUDSON_URL_ROOT}/job/#{@name}/config.xml"
105
+ response = send_xml_post_request(path, @config)
106
+ response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPRedirection)
107
+ end
108
+
109
+ # Set the repository url and update on Hudson server
110
+ def repository_url=(repository_url)
111
+ return false if @repository_url.nil?
112
+ @repository_url = repository_url
113
+ @config_doc.elements["/project/scm/locations/hudson.scm.SubversionSCM_-ModuleLocation/remote"].text = repository_url
114
+ @config = @config_doc.to_s
115
+ update
116
+ end
117
+
118
+ def repository_urls=(repository_urls)
119
+ return false if !repository_urls.class == Array
120
+ @repository_urls = repository_urls
121
+
122
+ i = 0
123
+ @config_doc.elements.each("/project/scm/locations/hudson.scm.SubversionSCM_-ModuleLocation") do |location|
124
+ location.elements["remote"].text = @repository_urls[i]
125
+ i += 1
126
+ end
127
+
128
+ @config = @config_doc.to_s
129
+ update
130
+ end
131
+
132
+ # Set the repository browser location and update on Hudson server
133
+ def repository_browser_location=(repository_browser_location)
134
+ @repository_browser_location = repository_browser_location
135
+ @config_doc.elements["/project/scm/browser/location"].text = repository_browser_location
136
+ @config = @config_doc.to_s
137
+ update
138
+ end
139
+
140
+ # Set the job description and update on Hudson server
141
+ def description=(description)
142
+ @description = description
143
+ @config_doc.elements["/project/description"].text = description
144
+ @config = @config_doc.to_s
145
+ update
146
+ end
147
+
148
+ # Start building this job on Hudson server (can't build parameterized jobs)
149
+ def build()
150
+ path = "#{HUDSON_URL_ROOT}/job/#{@name}/build"
151
+ response = send_post_request(path, {:delay => '0sec'})
152
+ response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPRedirection)
153
+ end
154
+
155
+ def disable()
156
+ path = "#{HUDSON_URL_ROOT}/job/#{@name}/disable"
157
+ response = send_post_request(path)
158
+ puts response.class
159
+ response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPRedirection)
160
+ end
161
+
162
+ def enable()
163
+ path = "#{HUDSON_URL_ROOT}/job/#{@name}/enable"
164
+ response = send_post_request(path)
165
+ puts response.class
166
+ response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPRedirection)
167
+ end
168
+
169
+ # Delete this job from Hudson server
170
+ def delete()
171
+ path = "#{HUDSON_URL_ROOT}/job/#{@name}/doDelete"
172
+ response = send_post_request(path)
173
+ response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPRedirection)
174
+ end
175
+
176
+ def wipe_out_workspace()
177
+ wait_for_build_to_finish
178
+ path = "#{HUDSON_URL_ROOT}/job/#{@name}/doWipeOutWorkspace"
179
+ if !active?
180
+ response = send_post_request(path)
181
+ else
182
+ response = false
183
+ end
184
+ response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPRedirection)
185
+ end
186
+ end
187
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hudson-remote-api
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Dru Ibarra
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-01 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Connect to Hudson's remote web API
23
+ email: Druwerd@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README
30
+ files:
31
+ - .gitignore
32
+ - README
33
+ - Rakefile
34
+ - VERSION
35
+ - hudson-remote-api.gemspec
36
+ - lib/hudson-remote-api.rb
37
+ - lib/hudson-remote-api/build.rb
38
+ - lib/hudson-remote-api/build_queue.rb
39
+ - lib/hudson-remote-api/errors.rb
40
+ - lib/hudson-remote-api/job.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/Druwerd/hudson-remote-api
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --charset=UTF-8
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ requirements: []
69
+
70
+ rubyforge_project: hudson-remote-api
71
+ rubygems_version: 1.3.7
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Connect to Hudson's remote web API
75
+ test_files: []
76
+