hudkins 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,86 @@
1
+ class Hudkins
2
+ # this class is just a convenience wrapper around an array of jobs.
3
+ # _FIX_ consider that Enumerable methods returns a Hudkins::Jobs obj instead of an array..
4
+ class Jobs
5
+ include Hudkins::Common
6
+ include Enumerable
7
+
8
+ def initialize(hudkins)
9
+ @hudkins = hudkins
10
+ data = @hudkins.get_parsed( "/api/json", :accept => "application/json" )
11
+ @jobs = Array.new
12
+ data["jobs"].each do |job|
13
+ @jobs << Hudkins::Job.new( @hudkins, job )
14
+ end
15
+ end
16
+
17
+ def inspect
18
+ super
19
+ end
20
+
21
+ # Enumerable
22
+ # This returns @jobs if no block_given.. so in effect we can use array
23
+ # methods like this: jobs.each.last
24
+ # is this hacky? Normal?
25
+ def each
26
+ @jobs.each do |job|
27
+ yield job if block_given?
28
+ end
29
+ end
30
+
31
+ def size
32
+ @jobs.size
33
+ end
34
+
35
+ ##
36
+ # === Description
37
+ # convenience method for returning all (or part) of just the names of jobs
38
+ def names name = ""
39
+ find_all_by_name( name ).map(&:name)
40
+ end
41
+
42
+ ##
43
+ # :method: find_by_
44
+ # === Description
45
+ # Implements find_by_ and find_all_by_ methods according to any method to
46
+ # which that job responds.
47
+ #
48
+ # === Examples
49
+ # jobs.find_by_name "job_name"
50
+ #
51
+ # jobs.find_all_by_url /svn/x
52
+
53
+ ##
54
+ # nodoc
55
+ def method_missing sym, *args, &block # :nodoc:
56
+ meth, name = missing_method_name( sym )
57
+ if name
58
+ self.send(meth) do |job|
59
+ # I can't remember why I'm using union... :/
60
+ # job.send(name).to_s.gsub(/-/, "_") =~ Regexp.union( args.map(&:to_s) )
61
+ # args is an array because of method_missing. but find(_all)_by only
62
+ # takes one parameter (for now..)
63
+ # to_s for symbols, also removes warnings if given a regexp with options.
64
+ arg = args.first.to_s #.gsub(/-/, "_")
65
+ regex = Regexp.new(arg, Regexp::IGNORECASE)
66
+ # gsub used so I can pass in symbols as names (which don't allow dashes
67
+ # to_s as job.url returns a URI
68
+ job.send(name).to_s.gsub(/-/, "_") =~ regex
69
+ end
70
+ else
71
+ super sym, *args, &block
72
+ end
73
+ end
74
+
75
+ def respond_to? sym
76
+ missing_method_name sym or super sym
77
+ end
78
+
79
+ private
80
+ def missing_method_name sym # :nodoc:
81
+ # (find|find_all)_by_(name|url|...)
82
+ [$1, $2] if sym.to_s =~ /^(.*)_by_(.*)$/ and Hudkins::Job.method_defined? $2
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,103 @@
1
+ #--
2
+ ##
3
+ # attr_accessor_from_config :method_name, "search_path"[, type]
4
+ # attr_reader_from_config :method_name, "search_path"[, type]
5
+ # attr_writer_from_config :method_name, "search_path"[, type]
6
+ #++
7
+
8
+ class Hudkins
9
+ ##
10
+ # extend Hudkins::Mixin to include DSL style methods
11
+ module Mixin
12
+ ##
13
+ # === Description
14
+ # Similar to attr_reader but takes additional parameters
15
+ #
16
+ # :bool type addes a method_name? alias
17
+ #
18
+ # === Examples:
19
+ # class MyObj
20
+ # attr_reader_from_config :disabled, "//project//disabled", :bool
21
+ # end
22
+ #
23
+ # MyObj.new.disabled? # => true
24
+ #
25
+ # === Parameters:
26
+ #
27
+ # +method_name+:: attr_reader method name
28
+ # +search_path+:: Nokogiri.at search path
29
+ # +type+:: :fixnum # => converts config content to integer
30
+ #
31
+ # :bool # => creates a boolean reader method. useful
32
+ # when the config returns string "true" but True class is
33
+ # desirable
34
+ # Class # => ie. a value of `Integer' will result in
35
+ # typecasting via Integer(value)
36
+ def attr_reader_from_config method_name, search_path, type = :default
37
+ # could we do something like inspect el.children.size? for arrays?
38
+ define_method method_name do
39
+ el = config.at(search_path)
40
+ if el
41
+ case type
42
+ when :bool then
43
+ /true/i === el.content
44
+ when :fixnum, :int then
45
+ el.content.to_i
46
+ when :array then
47
+ warn ":array not implemented yet"
48
+ #value = []
49
+ #el.children
50
+ when Class then
51
+ # Integer(value)
52
+ Kernel.send(type.name, el.content)
53
+ else
54
+ el.content
55
+ end
56
+ else
57
+ warn "`#{search_path}' was not found in config."
58
+ end
59
+ end
60
+ # use alias_method instead of explicitely defining the method so I can
61
+ # call the method_name without the ? in internal methods.
62
+ alias_method "#{method_name}?".to_sym, method_name if :bool === type
63
+ end
64
+
65
+ ##
66
+ # === Description
67
+ # see attr_reader_from_config
68
+ #
69
+ # update config node
70
+ def attr_writer_from_config method_name, search_path, type = :default
71
+ define_method "#{method_name}=".to_sym do |arg|
72
+ config.at(search_path).content = arg
73
+ arg
74
+ end
75
+ end
76
+
77
+ ##
78
+ # === Description
79
+ # see attr_reader_from_config
80
+ #
81
+ # (optionally) update config node and immediately post back the config then
82
+ # return the updated value
83
+ #
84
+ # `!' is added to end of method_name to signify post call
85
+ def attr_post_from_config method_name, search_path, type = :default
86
+ attr_reader_from_config method_name, search_path, type unless self.respond_to? method_name
87
+ define_method "#{method_name}!".to_sym do |arg|
88
+ # complains if arg isn't given..
89
+ send("#{method_name}=", arg) if arg
90
+ post_config!
91
+ self.send(method_name)
92
+ end
93
+ end
94
+
95
+ ##
96
+ # combines attr_{reader,writer,post}_from_config
97
+ def attr_accessor_from_config *args
98
+ attr_reader_from_config *args
99
+ attr_writer_from_config *args
100
+ attr_post_from_config *args
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,20 @@
1
+ require "rubygems"
2
+ require "rest_client"
3
+ require "json"
4
+ #require "active_record"
5
+ require "nokogiri"
6
+
7
+ $: << File.join( File.dirname( __FILE__ ), ".." )
8
+ require 'hudkins'
9
+
10
+ # Load our rakefile extensions
11
+ Dir["#{File.dirname(__FILE__)}/../tasks/**/*.rake"].sort.each { |ext| load ext }
12
+
13
+ ##
14
+ # rake hudkins:update job=rservices-m
15
+ #
16
+ # rake hudkins:update_main
17
+ # rake hudkins:update_release_candidate
18
+ # rake hudkins:update_in_production
19
+ #
20
+ # rake hudkins:update:rservices_main
@@ -0,0 +1,130 @@
1
+ class Hudkins
2
+ ##
3
+ # === Description
4
+ # Convenience class for RestClient responses
5
+ #
6
+ # RestClient.get returns (configurable via &block parameter to Hudkins#get)
7
+ # [#response, #request, #result]. I wanted to have all these available but
8
+ # not as an array :/
9
+ #
10
+ # === Example
11
+ # # get response headers
12
+ # hud.get( "/path" ).response.headers
13
+ #
14
+ # # do something based on response success
15
+ # response = hud.get "/path"
16
+ # raise response.message unless response.success?
17
+ class Response
18
+ attr_accessor :response, :request, :result
19
+ ##
20
+ # === Example
21
+ # hud.get("/path") do |*args|
22
+ # Hudkins::Response.new( *args )
23
+ # end
24
+ # # => returns Hudkins::Response object
25
+ def initialize response, request, result
26
+ @response = response
27
+ @request = request
28
+ @result = result
29
+ end
30
+
31
+ ##
32
+ # === Description
33
+ # response = get "/path"
34
+ # JSON.pasre response.body
35
+ def body
36
+ response.body
37
+ end
38
+
39
+ ##
40
+ # === Description
41
+ # response status code
42
+ def code
43
+ result.code.to_i
44
+ end
45
+
46
+ ##
47
+ # === Description
48
+ # RestClient response error
49
+ def errors
50
+ RestClient::Exceptions::EXCEPTIONS_MAP[response.code].new response
51
+ end
52
+
53
+ ##
54
+ # === Description
55
+ # #errors message
56
+ def message
57
+ errors.message
58
+ end
59
+
60
+ ##
61
+ # === Description
62
+ # was the response successful?
63
+ # does a simple test if the response code was 2xx
64
+ def success?
65
+ # I think RestClient::Response doesn't use Net::HTTP... any more, but I
66
+ # couldn't figure out how they want you to do this without doing case
67
+ # format or something like RestClient::Ok.. etc. (There is no equivalent
68
+ # RestClient::Success, although there is a RestClient::Redirect
69
+ case result
70
+ when Net::HTTPSuccess, Net::HTTPRedirection then
71
+ true
72
+ else
73
+ false
74
+ end
75
+ #case code
76
+ #when (200..299)
77
+ #true
78
+ #when (302)
79
+ ## Found. seems to be status code for successful posts.. :/
80
+ #true
81
+ #else
82
+ #false
83
+ #end
84
+ end
85
+ # http_body.match(/<h1>Error<\/h1><p>([^<]*)<\/p>/)[0]
86
+
87
+ ##
88
+ # Hudson returns type :javascript for get "/api/json" even if I explicietly set the :accept header
89
+ # and :html for config.xml
90
+ # so I'm going to use the request headers to get the returned content type... blah
91
+
92
+ def response_type
93
+ # unbelievable hudson!
94
+ # this probably isn't the best way, but MIME::Types returns an array...
95
+ MIME::Type.new request.headers[:accept]
96
+ end
97
+
98
+ def type
99
+ response_type.sub_type.to_sym
100
+ end
101
+ end
102
+
103
+ # the idea behind this is that if the hudson server isn't available,
104
+ # getaddrinfo has a really long timeout.. like over 20 seconds! This is
105
+ # really a DNS issue, but I still don't want to wait that long and I couldn't
106
+ # figure out how to set a better timeout.
107
+ module HostLookup
108
+ # defaults to 2
109
+ TIMEOUT = 2
110
+ ##
111
+ # timeout Socket.getaddrinfo after TIMEOUT seconds
112
+ #
113
+ # returns true/false
114
+ def self.available? uri, timeout = nil
115
+ timeout ||= TIMEOUT
116
+ pid = fork do
117
+ Socket.getaddrinfo( uri.host, uri.scheme )
118
+ end
119
+ begin
120
+ Timeout::timeout(timeout) do
121
+ Process.wait(pid)
122
+ end
123
+ true
124
+ rescue Timeout::Error
125
+ Process.kill(9, pid)
126
+ false
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,11 @@
1
+
2
+
3
+ def find_svn_url host_name
4
+ url = "http://example.com/_priv/sysinfo"
5
+ uri = URI.parse url
6
+ uri.host = host_name
7
+ body = RestClient.get uri.to_s
8
+ # is there a better way to do this? Rservices and Syndication differ in where
9
+ # they put this info
10
+ svn_url = body.scan(/repository":"([^ ]*) r\d*",/).first
11
+ end
@@ -0,0 +1,30 @@
1
+ <?xml version="1.0" encoding="UTF-8"?><project>
2
+ <actions/>
3
+ <description/>
4
+ <keepDependencies>false</keepDependencies>
5
+ <properties/>
6
+ <scm class="hudson.scm.SubversionSCM">
7
+ <locations>
8
+ <hudson.scm.SubversionSCM_-ModuleLocation>
9
+ <remote>https://subversion/project_name/branches/current_branch</remote>
10
+ <local>.</local>
11
+ </hudson.scm.SubversionSCM_-ModuleLocation>
12
+ </locations>
13
+ <useUpdate>true</useUpdate>
14
+ <doRevert>false</doRevert>
15
+ <excludedRegions/>
16
+ <includedRegions/>
17
+ <excludedUsers/>
18
+ <excludedRevprop/>
19
+ <excludedCommitMessages/>
20
+ </scm>
21
+ <canRoam>true</canRoam>
22
+ <disabled>true</disabled>
23
+ <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
24
+ <triggers class="vector"/>
25
+ <concurrentBuild>false</concurrentBuild>
26
+ <builders/>
27
+ <publishers/>
28
+ <buildWrappers/>
29
+ </project>
30
+
@@ -0,0 +1,22 @@
1
+ {"assignedLabels":[{}],
2
+ "mode":"NORMAL",
3
+ "nodeDescription":"the master Hudson node",
4
+ "nodeName":"",
5
+ "numExecutors":2,
6
+ "description":null,
7
+ "jobs":[
8
+ {"name":"project_name",
9
+ "url":"http://example.com/job/project_name/",
10
+ "color":"blue"},
11
+ {"name":"project_name2",
12
+ "url":"http://example.com/job/project_name2/",
13
+ "color":"blue"}],
14
+ "overallLoad":{},
15
+ "primaryView":{"name":"All",
16
+ "url":"http://example.com/"},
17
+ "slaveAgentPort":0,
18
+ "useCrumbs":false,
19
+ "useSecurity":false,
20
+ "views":[{"name":"All",
21
+ "url":"http://example.com/"}]}
22
+
@@ -0,0 +1,14 @@
1
+ <?xml version='1.0' encoding='UTF-8'?>
2
+ <project>
3
+ <keepDependencies>false</keepDependencies>
4
+ <properties/>
5
+ <scm class="hudson.scm.NullSCM"/>
6
+ <canRoam>true</canRoam>
7
+ <disabled>false</disabled>
8
+ <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
9
+ <triggers class="vector"/>
10
+ <concurrentBuild>false</concurrentBuild>
11
+ <builders/>
12
+ <publishers/>
13
+ <buildWrappers/>
14
+ </project>
@@ -0,0 +1,50 @@
1
+ require "rubygems"
2
+ require 'minitest/unit'
3
+ require "mocha"
4
+ require "ostruct"
5
+ require "erb"
6
+ MiniTest::Unit.autorun
7
+
8
+
9
+ class MiniTest::Unit::TestCase
10
+
11
+ # minitest probably already has a fixtures something. but I don't have access
12
+ # to the docs currently.
13
+ # mock_files.config # => test/fixtures/config.erb
14
+ def mock_files
15
+ @mock_files ||= begin
16
+ obj = OpenStruct.new
17
+ glob = File.join( File.dirname(__FILE__), "fixtures", "*.erb")
18
+ files = Dir.glob( glob )
19
+ files.each do |file|
20
+ name = File.basename( file, ".erb" ).gsub(/\.-/, "_")
21
+ result = File.read( file )
22
+ content = ERB.new(result, 0, "%<>").result(binding)
23
+ obj.send "#{name}=", content # ruby is pretty amazing!
24
+ end
25
+ obj
26
+ end
27
+ end # mock_files
28
+
29
+ def hudkins_setup
30
+ @host = "http://example.com"
31
+ @hud = Hudkins.new @host
32
+ @mock_response = mock("response").responds_like(Hudkins::Response.new 1,2,3)
33
+ @mock_response.stubs(:success?).returns(true)
34
+ mock_rc_resource(:get, mock_files.jobs, :json)
35
+ @hud.jobs
36
+ end
37
+
38
+ def mock_rc_resource method, body = nil, type = nil
39
+ @mock_response.stubs(:body).returns(body) if body
40
+ @mock_response.stubs(:type).returns(type) if type
41
+ rc_request = RestClient::Request.expects(:execute).with(has_entry(:method => method))
42
+ if block_given?
43
+ yield(rc_request)
44
+ else
45
+ rc_request.returns( @mock_response )
46
+ end
47
+ end
48
+
49
+
50
+ end # MiniTest::Unit::TestCase