hudkins 0.0.1

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,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