hudson-remote-api 0.7.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/Gemfile +9 -0
  4. data/Gemfile.lock +20 -0
  5. data/README.md +11 -9
  6. data/Rakefile +10 -0
  7. data/fixtures/vcr_cassettes/TestHudsonBuildQueue_test_list.yml +38 -0
  8. data/fixtures/vcr_cassettes/TestHudsonBuild_setup.yml +407 -0
  9. data/fixtures/vcr_cassettes/TestHudsonBuild_test_build_info.yml +222 -0
  10. data/fixtures/vcr_cassettes/TestHudsonClient_test_build_job_.yml +52 -0
  11. data/fixtures/vcr_cassettes/TestHudsonClient_test_build_job_with_parameters_.yml +52 -0
  12. data/fixtures/vcr_cassettes/TestHudsonClient_test_build_queue_info.yml +38 -0
  13. data/fixtures/vcr_cassettes/TestHudsonClient_test_create_item_.yml +52 -0
  14. data/fixtures/vcr_cassettes/TestHudsonClient_test_delete_job_.yml +52 -0
  15. data/fixtures/vcr_cassettes/TestHudsonClient_test_job_build_info.yml +39 -0
  16. data/fixtures/vcr_cassettes/TestHudsonClient_test_job_config_info.yml +48 -0
  17. data/fixtures/vcr_cassettes/TestHudsonJob_test_build.yml +409 -0
  18. data/fixtures/vcr_cassettes/TestHudsonJob_test_build_with_params.yml +189 -0
  19. data/fixtures/vcr_cassettes/TestHudsonJob_test_builds_list.yml +192 -0
  20. data/fixtures/vcr_cassettes/TestHudsonJob_test_copy.yml +410 -0
  21. data/fixtures/vcr_cassettes/TestHudsonJob_test_create.yml +257 -0
  22. data/fixtures/vcr_cassettes/TestHudsonJob_test_desc_update.yml +281 -0
  23. data/fixtures/vcr_cassettes/TestHudsonJob_test_get.yml +191 -0
  24. data/fixtures/vcr_cassettes/TestHudsonJob_test_job_with_spaces.yml +260 -0
  25. data/fixtures/vcr_cassettes/TestHudsonJob_test_list.yml +39 -0
  26. data/fixtures/vcr_cassettes/TestHudsonJob_test_list_active.yml +39 -0
  27. data/fixtures/vcr_cassettes/TestHudsonJob_test_new.yml +561 -0
  28. data/fixtures/vcr_cassettes/TestHudsonJob_test_scm_url.yml +728 -0
  29. data/fixtures/vcr_cassettes/TestHudsonJob_test_triggers_delete.yml +588 -0
  30. data/fixtures/vcr_cassettes/TestHudsonJob_test_triggers_set.yml +346 -0
  31. data/fixtures/vcr_cassettes/TestHudsonJob_test_triggers_set_using_shortcut.yml +346 -0
  32. data/fixtures/vcr_cassettes/TestHudsonJob_test_url.yml +156 -0
  33. data/fixtures/vcr_cassettes/TestHudsonJob_test_wipe_out_workspace.yml +685 -0
  34. data/hudson-remote-api.gemspec +23 -0
  35. data/lib/hudson-remote-api.rb +3 -128
  36. data/lib/hudson-remote-api/build.rb +18 -35
  37. data/lib/hudson-remote-api/build_queue.rb +13 -20
  38. data/lib/hudson-remote-api/client.rb +179 -0
  39. data/lib/hudson-remote-api/hudson_xml_api.rb +61 -0
  40. data/lib/hudson-remote-api/job.rb +207 -345
  41. data/lib/hudson-remote-api/parser/build_info.rb +37 -0
  42. data/lib/hudson-remote-api/parser/build_queue_info.rb +19 -0
  43. data/lib/hudson-remote-api/parser/job_config_info.rb +99 -0
  44. data/lib/hudson-remote-api/parser/job_info.rb +62 -0
  45. data/lib/hudson-remote-api/parser/multicast.rb +28 -0
  46. data/lib/hudson-remote-api/parser/server_info.rb +49 -0
  47. data/lib/hudson-remote-api/settings.rb +31 -0
  48. data/lib/hudson-remote-api/version.rb +3 -0
  49. data/lib/hudson-remote-api/xml_writer/job_config_info.rb +113 -0
  50. data/test/test_helper.rb +10 -0
  51. data/test/test_hudson_build.rb +28 -0
  52. data/test/test_hudson_build_queue.rb +11 -0
  53. data/test/test_hudson_client.rb +65 -0
  54. data/test/test_hudson_job.rb +162 -0
  55. data/test/test_hudson_multicast.rb +9 -0
  56. data/test/test_hudson_settings.rb +70 -0
  57. metadata +71 -53
  58. data/lib/hudson-remote-api/config.rb +0 -46
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "hudson-remote-api/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "hudson-remote-api"
7
+ s.version = Hudson::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Dru Ibarra"]
10
+ s.email = ["Druwerd@gmail.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Connect to Hudson's remote web API}
13
+ s.description = %q{Connect to Hudson's remote web API}
14
+
15
+ #s.add_development_dependency "rspec"
16
+
17
+ s.rubyforge_project = "hudson-remote-api"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
@@ -1,130 +1,5 @@
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 "net/https"
7
1
  require 'uri'
8
- require 'rexml/document'
9
- require 'cgi'
10
- require 'yaml'
11
- require 'zlib'
12
- require File.dirname(__FILE__) + '/hudson-remote-api/config.rb'
13
-
14
- module Hudson
15
- # Base class for all Hudson objects
16
- class HudsonObject
17
-
18
- def self.hudson_request(uri,request)
19
- Net::HTTP.start(uri.host, uri.port) do |http|
20
- http = Net::HTTP.new(uri.host, uri.port)
21
- if uri.scheme == 'https'
22
- http.use_ssl = true
23
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
24
- end
25
- http.request(request)
26
- end
27
- end
28
-
29
- def self.load_xml_api
30
- @@hudson_xml_api_path = File.join(Hudson[:url], "api/xml")
31
- @@xml_api_create_item_path = File.join(Hudson[:url], "createItem")
32
- end
33
-
34
- load_xml_api
35
-
36
- def self.url_for(path)
37
- File.join Hudson[:url], path
38
- end
39
-
40
- def self.get_xml(url)
41
- uri = URI.parse(URI.encode(url))
42
- host = uri.host
43
- port = uri.port
44
- path = uri.path
45
- request = Net::HTTP::Get.new(path)
46
- request.basic_auth(Hudson[:user], Hudson[:password]) if Hudson[:user] and Hudson[:password]
47
- request['Content-Type'] = "text/xml"
48
- response = hudson_request(uri,request)
49
-
50
- if response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPRedirection)
51
- encoding = response.get_fields("Content-Encoding")
52
- if encoding and encoding.include?("gzip")
53
- return Zlib::GzipReader.new(StringIO.new(response.body)).read
54
- else
55
- return response.body
56
- end
57
- else
58
- $stderr.puts response.body
59
- raise APIError, "Error retrieving #{path}"
60
- end
61
- end
62
-
63
- def get_xml(path)
64
- self.class.get_xml(path)
65
- end
66
-
67
- def self.send_post_request(url, data={})
68
- uri = URI.parse(URI.encode(url))
69
- host = uri.host
70
- port = uri.port
71
- path = uri.path
72
- request = Net::HTTP::Post.new(path)
73
- request.basic_auth(Hudson[:user], Hudson[:password]) if Hudson[:user] and Hudson[:password]
74
- request.set_form_data(data)
75
- request.add_field(crumb.name, crumb.value) if crumb
76
- hudson_request(uri,request)
77
- end
78
-
79
- def send_post_request(url, data={})
80
- self.class.send_post_request(url, data)
81
- end
82
-
83
- def self.send_xml_post_request(url, xml, data=nil)
84
- uri = URI.parse(URI.encode(url))
85
- host = uri.host
86
- port = uri.port
87
- path = uri.path
88
- path = path+"?"+uri.query if uri.query
89
- request = Net::HTTP::Post.new(path)
90
- request.basic_auth(Hudson[:user], Hudson[:password]) if Hudson[:user] and Hudson[:password]
91
- request.set_form_data(data) if data
92
- request.add_field(crumb.name, crumb.value) if crumb
93
- request.body = xml
94
- hudson_request(uri,request)
95
- end
96
-
97
- def send_xml_post_request(url, xml, data=nil)
98
- self.class.send_xml_post_request(url, xml, data)
99
- end
100
-
101
-
102
-
103
- def self.crumb
104
- @@apiCrumb ||= nil
105
- end
106
-
107
- def self.fetch_crumb
108
- if Hudson[:crumb]
109
- body = get_xml(url_for '/crumbIssuer/api/xml')
110
- doc = REXML::Document.new(body)
111
-
112
- crumbValue = doc.elements['/defaultCrumbIssuer/crumb'] or begin
113
- $stderr.puts "Failure fetching crumb value from server"
114
- return
115
- end
116
-
117
- crumbName = doc.elements['/defaultCrumbIssuer/crumbRequestField'] or begin
118
- $stderr.puts "Failure fetching crumb field name from server"
119
- return
120
- end
121
-
122
- @@apiCrumb = Struct.new(:name,:value).new(crumbName.text,crumbValue.text)
123
- end
124
- rescue
125
- $stderr.puts "Failure fetching crumb xml"
126
- end
127
- end
128
- end
2
+ require 'net/http'
3
+ require './lib/hudson-remote-api/client.rb'
129
4
 
130
- Dir[File.dirname(__FILE__) + '/hudson-remote-api/*.rb'].each {|file| require file }
5
+ Dir[File.dirname(__FILE__) + '/hudson-remote-api/**/*.rb'].each {|file| require file }
@@ -1,41 +1,24 @@
1
1
  module Hudson
2
- class Build < HudsonObject
3
- attr_reader :number, :job, :revisions, :result, :culprit
4
-
5
- def initialize(job, build_number=nil)
6
- @job = Job.new(job) if job.kind_of?(String)
7
- @job = job if job.kind_of?(Hudson::Job)
8
- if build_number
9
- @number = build_number
10
- else
11
- @number = @job.last_build
12
- end
13
- @revisions = {}
14
- @xml_api_build_info_path = File.join(Hudson[:url], "job/#{@job.name}/#{@number}/api/xml")
15
- load_build_info
16
- end
17
-
18
- private
19
- def load_build_info
20
-
21
- build_info_xml = patch_bad_git_xml(get_xml(@xml_api_build_info_path))
22
- build_info_doc = REXML::Document.new(build_info_xml)
23
-
24
- if build_info_doc.elements["/freeStyleBuild/result"]
25
- @result = build_info_doc.elements["/freeStyleBuild/result"].text
26
- end
27
- if !build_info_doc.elements["/freeStyleBuild/changeSet"].nil?
28
- build_info_doc.elements.each("/freeStyleBuild/changeSet/revision"){|e| @revisions[e.elements["module"].text] = e.elements["revision"].text }
29
- end
2
+ class Build
3
+ attr_reader :number, :job, :revisions, :result, :culprit
30
4
 
31
- if build_info_doc.elements['/freeStyleBuild/culprit/fullName']
32
- @culprit = build_info_doc.elements['/freeStyleBuild/culprit/fullName'].text
33
- end
5
+ def initialize(job, build_number=nil)
6
+ @job = Job.new(job) if job.kind_of?(String)
7
+ @job = job if job.kind_of?(Hudson::Job)
8
+ @number = build_number || @job.last_build
9
+ @revisions = {}
10
+ load_build_info
11
+ end
34
12
 
35
- end
13
+ private
14
+ def load_build_info
15
+ build_info_xml = Hudson.client.job_build_info(self.job.name, self.number)
16
+ build_info_parser = Hudson::Parser::BuildInfo.new(build_info_xml)
36
17
 
37
- def patch_bad_git_xml(xml)
38
- xml.gsub(/<(\/?)origin\/([_a-zA-Z0-9\-\.]+)>/, '<\1origin-\2>')
18
+ @result = build_info_parser.result
19
+ @revisions = build_info_parser.revisions
20
+ @culprit = build_info_parser.culprit
39
21
  end
40
- end
22
+
23
+ end
41
24
  end
@@ -1,23 +1,16 @@
1
1
  module Hudson
2
- # This class provides an interface to Hudson's build queue
3
- class BuildQueue < HudsonObject
4
-
5
- def self.load_xml_api
6
- @@xml_api_build_queue_info_path = File.join(Hudson[:url], "queue/api/xml")
7
- end
8
-
9
- load_xml_api
10
-
11
- # List the jobs in the queue
12
- def self.list()
13
- xml = get_xml(@@xml_api_build_queue_info_path)
14
- queue = []
15
- queue_doc = REXML::Document.new(xml)
16
- return queue if queue_doc.elements["/queue/item"].nil?
17
- queue_doc.each_element("/queue/item/task") do |job|
18
- queue << job.elements["name"].text
19
- end
20
- queue
21
- end
2
+ # This class provides an interface to Hudson's build queue
3
+ class BuildQueue
4
+
5
+ class << self
6
+ def list
7
+ xml = Hudson.client.build_queue_info
8
+ build_queue_info_parser = Hudson::Parser::BuildQueueInfo.new(xml)
9
+
10
+ build_queue_info_parser.items
11
+ end
22
12
  end
13
+
14
+ end
15
+
23
16
  end
@@ -0,0 +1,179 @@
1
+ require File.dirname(__FILE__) + '/multicast.rb'
2
+
3
+ module Hudson
4
+
5
+ class << self
6
+ def client(config_settings={})
7
+ @client ||= Client.new(config_settings)
8
+ end
9
+ end
10
+
11
+ class Client
12
+ attr_reader :configuration, :xml_api
13
+
14
+ def initialize(config_settings={})
15
+ @configuration = ::Hudson::Settings.new(config_settings).configuration
16
+ @xml_api = ::Hudson::XmlApi.new(self.configuration)
17
+ fetch_crumb
18
+ end
19
+
20
+ def auto_configure
21
+ xml_response = ::Hudson.discover
22
+ if xml_response
23
+ mulitcast_parser = ::Hudson::Parser::Multicast.new(xml_response)
24
+ self.configuration.host = mulitcast_parser.url || self.configuration.host
25
+ self.configuration.version = mulitcast_parser.version || self.configuration.version
26
+ puts "found Hudson version #{mulitcast_parser.version} @ #{mulitcast_parser.url}"
27
+ return !mulitcast_parser.url.nil?
28
+ end
29
+ end
30
+
31
+ def job_build_info(job_name, build_number)
32
+ get_xml(self.xml_api.build_info_url(job_name, build_number))
33
+ end
34
+
35
+ def build_queue_info
36
+ get_xml(self.xml_api.build_queue_info_url)
37
+ end
38
+
39
+ def build_job!(job_name, delay=0)
40
+ send_post_request(self.xml_api.build_url(job_name), {:delay => "#{delay}sec"})
41
+ end
42
+
43
+ def build_job_with_parameters!(job_name, params, delay=0)
44
+ send_post_request(self.xml_api.build_with_parameters_url(job_name), {:delay => "#{delay}sec"}.merge(params) )
45
+ end
46
+
47
+ def job_config_info(job_name)
48
+ get_xml(self.xml_api.job_config_url(job_name))
49
+ end
50
+
51
+ def create_item!(params)
52
+ send_post_request(self.xml_api.create_item_url, params)
53
+ end
54
+
55
+ def delete_job!(job_name)
56
+ send_post_request(self.xml_api.delete_url(job_name))
57
+ end
58
+
59
+ def disable_job!(job_name)
60
+ send_post_request(self.xml_api.disable_url(job_name))
61
+ end
62
+
63
+ def enable_job!(job_name)
64
+ send_post_request(self.xml_api.enable_url(job_name))
65
+ end
66
+
67
+ def job_info(job_name)
68
+ get_xml(self.xml_api.job_info_url(job_name))
69
+ end
70
+
71
+ def server_info
72
+ get_xml(self.xml_api.server_info_url)
73
+ end
74
+
75
+ def update_job_config!(job_name, config)
76
+ send_xml_post_request(self.xml_api.job_config_url(job_name), config)
77
+ end
78
+
79
+ def wipeout_job_workspace!(job_name)
80
+ send_post_request(self.xml_api.wipeout_workspace_url(job_name))
81
+ end
82
+
83
+ private
84
+ def patch_bad_git_xml(xml)
85
+ xml.gsub(/<(\/?)origin\/([_a-zA-Z0-9\-\.]+)>/, '<\1origin-\2>')
86
+ end
87
+
88
+ def http_class
89
+ if self.configuration.proxy_host && self.configuration.proxy_port
90
+ ::Net::HTTP::Proxy(self.configuration.proxy_host, self.configuration.proxy_port)
91
+ else
92
+ ::Net::HTTP
93
+ end
94
+ end
95
+
96
+ def hudson_request(uri,request)
97
+ http_class.start(uri.host, uri.port) do |http|
98
+ http = http_class.new(uri.host, uri.port)
99
+ if uri.scheme == 'https'
100
+ http.use_ssl = true
101
+ http.verify_mode = ::OpenSSL::SSL::VERIFY_NONE
102
+ end
103
+ http.request(request)
104
+ end
105
+ end
106
+
107
+ def get_xml(url)
108
+ uri = ::URI.parse(URI.encode(url))
109
+ request = http_class::Get.new(uri.path).tap do |r|
110
+ r.basic_auth(self.configuration.user, self.configuration.password) if self.configuration.user && self.configuration.password
111
+ r['Content-Type'] = "text/xml"
112
+ end
113
+
114
+ response = hudson_request(uri,request)
115
+
116
+ if response.is_a?(::Net::HTTPSuccess) or response.is_a?(::Net::HTTPRedirection)
117
+ encoding = response.get_fields("Content-Encoding")
118
+ if encoding and encoding.include?("gzip")
119
+ return ::Zlib::GzipReader.new(::StringIO.new(response.body)).read
120
+ else
121
+ return response.body
122
+ end
123
+ else
124
+ $stderr.puts response.body
125
+ raise APIError, "Error retrieving #{uri.path}"
126
+ end
127
+ end
128
+
129
+ def send_post_request(url, data={})
130
+ uri = ::URI.parse(::URI.encode(url))
131
+ request = http_class::Post.new(uri.path).tap do |r|
132
+ r.basic_auth(self.configuration.user, self.configuration.password) if self.configuration.user && self.configuration.password
133
+ r.set_form_data(data)
134
+ r.add_field(crumb.name, crumb.value) if crumb
135
+ end
136
+
137
+ hudson_request(uri,request)
138
+ end
139
+
140
+ def send_xml_post_request(url, xml, data=nil)
141
+ uri = ::URI.parse(URI.encode(url))
142
+ path = uri.query ? "#{uri.path}?#{uri.query}" : uri.path
143
+ request = http_class::Post.new(path).tap do |r|
144
+ r.basic_auth(self.configuration.user, self.configuration.password) if self.configuration.user && self.configuration.password
145
+ r.set_form_data(data) if data
146
+ r.add_field(crumb.name, crumb.value) if crumb
147
+ r.body = xml
148
+ end
149
+
150
+ hudson_request(uri,request)
151
+ end
152
+
153
+ def crumb
154
+ @@apiCrumb ||= nil
155
+ end
156
+
157
+ def fetch_crumb
158
+ if self.configuration.crumb
159
+ body = get_xml(self.xml_api.crumb_url)
160
+ doc = ::REXML::Document.new(body)
161
+
162
+ crumbValue = doc.elements['/defaultCrumbIssuer/crumb'] or begin
163
+ $stderr.puts "Failure fetching crumb value from server"
164
+ return
165
+ end
166
+
167
+ crumbName = doc.elements['/defaultCrumbIssuer/crumbRequestField'] or begin
168
+ $stderr.puts "Failure fetching crumb field name from server"
169
+ return
170
+ end
171
+
172
+ @@apiCrumb = ::Struct.new(:name,:value).new(crumbName.text,crumbValue.text)
173
+ end
174
+ rescue
175
+ $stderr.puts "Failure fetching crumb xml"
176
+ end
177
+
178
+ end
179
+ end
@@ -0,0 +1,61 @@
1
+ module Hudson
2
+ class XmlApi
3
+ attr_accessor :host
4
+
5
+ def initialize(configuration)
6
+ self.host = configuration.host
7
+ end
8
+
9
+ def build_url(job_name)
10
+ File.join(self.host, "job/#{job_name}/build")
11
+ end
12
+
13
+ def build_info_url(job_name, build_number)
14
+ File.join(self.host, "job/#{job_name}/#{build_number}/api/xml")
15
+ end
16
+
17
+ def build_queue_info_url
18
+ File.join(self.host, "queue/api/xml")
19
+ end
20
+
21
+ def build_with_parameters_url(job_name)
22
+ File.join(self.host, "job/#{job_name}/buildWithParameters")
23
+ end
24
+
25
+ def create_item_url
26
+ File.join(self.host, "createItem")
27
+ end
28
+
29
+ def crumb_url
30
+ File.join(self.host, "/crumbIssuer/api/xml")
31
+ end
32
+
33
+ def delete_url(job_name)
34
+ File.join(self.host, "job/#{job_name}/doDelete")
35
+ end
36
+
37
+ def disable_url(job_name)
38
+ File.join(self.host, "job/#{job_name}/disable")
39
+ end
40
+
41
+ def enable_url(job_name)
42
+ File.join(self.host, "job/#{job_name}/enable")
43
+ end
44
+
45
+ def job_info_url(job_name)
46
+ File.join(self.host, "job/#{job_name}/api/xml")
47
+ end
48
+
49
+ def job_config_url(job_name)
50
+ File.join(self.host, "job/#{job_name}/config.xml")
51
+ end
52
+
53
+ def server_info_url
54
+ File.join(self.host, "api/xml")
55
+ end
56
+
57
+ def wipeout_workspace_url(job_name)
58
+ File.join(self.host, "job/#{job_name}/doWipeOutWorkspace")
59
+ end
60
+ end
61
+ end