hudson-remote-api 0.7.0 → 1.0.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.
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