hudkins 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,127 @@
1
+ require "hudkins"
2
+ require "optparse"
3
+ require "yaml"
4
+
5
+ ##
6
+ # === Description
7
+ # Main class for the included bin to interact with Hudson at the command line.
8
+ #
9
+ # The Commands are setup in Hudkins::Command::Exec
10
+ #
11
+ # === Usage
12
+ # see `hudkins -h'
13
+ #
14
+ # a host option is required. Can be configured in env variable +hudkins_host+ or
15
+ # config file. all options can be specified in a yaml file in +~/.hudkinsrc+
16
+ # and/or +`pwd`/.hudkinsrc+
17
+ class Hudkins::Command
18
+ include Hudkins::Common
19
+
20
+ require "hudkins/command/exec"
21
+ # all the run_ commands
22
+ extend Hudkins::Command::Exec
23
+
24
+ class << self
25
+ def run
26
+ parse_options
27
+ begin
28
+ return_string = send( @command )
29
+ puts return_string if return_string
30
+ rescue => e
31
+ raise_usage e.message
32
+ end
33
+ if @options[:irb]
34
+ run_start_irb
35
+ end
36
+ end
37
+
38
+ # commands:
39
+ # helpers:
40
+ def usage_msg
41
+ usage = <<-EOB
42
+
43
+ Usage: hudkins [opts] commands [job_name]
44
+
45
+ Commands: unambiguous_partial_command
46
+ build: start a job building.
47
+ config: pretty print job config. job_name
48
+ create: create new hudson job.
49
+ host: print hudson host url
50
+ list: list jobs. [job_name used as filter].
51
+
52
+
53
+ Optional: job_name (depends on command)
54
+ may also be partial (picks first match)
55
+
56
+ EOB
57
+ end
58
+
59
+ def raise_usage msg = nil
60
+ warn msg if msg
61
+ puts usage_msg
62
+ exit 1
63
+ end
64
+
65
+ def parse_options
66
+ # initialize cmd_list. This seems hacky, but I like having the cmd_list
67
+ # near the usage statement.
68
+ @options = {}
69
+
70
+ OptionParser.new do |opts|
71
+ opts.banner = usage_msg
72
+
73
+ opts.separator ""
74
+ opts.separator "Specific options:"
75
+
76
+ opts.on("--hud-host HOST", "Hudson host to connect to. Overrides rc file") do |h|
77
+ @options[:host] = h
78
+ end
79
+
80
+ opts.on("-i", "--irb", "Drop into irb.") do |i|
81
+ @options[:irb] = i
82
+ end
83
+ opts.on("-v", "--[no-]verbose", "Turn on verbose messages.") do |v|
84
+ $VERBOSE = @options[:verbose] = v
85
+ end
86
+
87
+ opts.separator ""
88
+ opts.separator "Common options:"
89
+
90
+ opts.on_tail("--version", "Show version") do
91
+ puts "Hudkins (#{Hudkins::VERSION}) (c) 2010"
92
+ exit
93
+ end
94
+
95
+ opts.on_tail("-h", "--help", "Show this message") do
96
+ puts opts
97
+ exit
98
+ end
99
+
100
+ end.parse!
101
+
102
+ config.merge! @options
103
+
104
+ @command, @job_name = ARGV.shift(2)
105
+ @command ||= "default"
106
+
107
+ @hud = Hudkins.new hud_host
108
+ job @job_name
109
+
110
+ parse_command
111
+ end
112
+
113
+ def parse_command
114
+ # select unambiguous command to run.
115
+ cmds = cmd_list.select {|c| Regexp.new(@command.to_s, Regexp::IGNORECASE) === c}
116
+ case cmds.size
117
+ when 0 then
118
+ raise_usage "#{@command} is not recognized as a valid command."
119
+ when 1 then
120
+ @command = "run_" << cmds.first
121
+ else
122
+ raise_usage "Ambiguous command. Please specify:\n #{cmds.join("\n ")}\n\n"
123
+ end
124
+ end
125
+
126
+ end
127
+ end
@@ -0,0 +1,113 @@
1
+ class Hudkins
2
+ ##
3
+ # === Description
4
+ # mixin for interacting with the lib
5
+ #
6
+ # the bin is setup to run any command prefaced with `run_'
7
+ # each command should return something to be puts or nil
8
+ module Command::Exec
9
+ ##
10
+ # default command. here as a place holder
11
+ def run_default
12
+ end
13
+
14
+ def run_build
15
+ "building!!!"
16
+ end
17
+
18
+ def run_config job_name = @job_name
19
+ required_params job_name => "job_name is required for this command."
20
+ [
21
+ job( job_name ).name,
22
+ job( job_name ).config
23
+ ]
24
+ end
25
+
26
+ def run_create job_name = @job_name
27
+ required_params job_name => "job_name is required for this command."
28
+ "creating!!!"
29
+ end
30
+
31
+ def run_host
32
+ hud_host
33
+ end
34
+
35
+ def run_list job_name = @job_name
36
+ names( job_name || "" )
37
+ end
38
+
39
+ def run_start_irb
40
+ puts <<-EOS
41
+
42
+ ---
43
+ Welcome to hudkins irb console.
44
+ type hudkins for help.
45
+
46
+ EOS
47
+ require "hudkins/command/irb_start"
48
+ # turn job names into methods
49
+ extend Hudkins::Command::Irb
50
+ IRB.start_session(nil, binding)
51
+ end
52
+
53
+ # helper commands
54
+
55
+ def hud
56
+ @hud
57
+ end
58
+
59
+ def job job_name = nil
60
+ if job_name
61
+ @job = hud.jobs.find_by_name job_name
62
+ end
63
+ @job
64
+ end
65
+
66
+ # could probably be cleaned up
67
+ def required_params values
68
+ e = Hudkins::ArgumentError.new ""
69
+ raize = false
70
+ values.each do |k,msg|
71
+ e << msg unless k
72
+ raize = true unless k
73
+ end
74
+ raise e if raize
75
+ end
76
+
77
+ def cmd_list
78
+ command_list.map {|m| m.gsub(/^run_/, '')}
79
+ end
80
+
81
+ def command_list
82
+ self.methods.select {|m| m =~ /^run_/}
83
+ end
84
+
85
+ def names name = ""
86
+ hud.jobs.names name
87
+ end
88
+
89
+ def hud_host
90
+ ENV["hudkins_host"] || config[:host] || raise_usage( "no hudkins_host defined." )
91
+ end
92
+
93
+ def config
94
+ @config ||= load_rc
95
+ end
96
+
97
+ def load_rc
98
+ hudkins_rc.inject({}) do |ret, rc|
99
+ h = YAML.load_file( rc )
100
+ ret.merge! h if h
101
+ ret
102
+ end
103
+ end
104
+
105
+ def hudkins_rc
106
+ [
107
+ File.expand_path("~/.hudkinsrc"),
108
+ File.join(Dir.pwd, ".hudkinsrc")
109
+ ].select {|f| File.size? f} # exists and is non 0
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,86 @@
1
+ require "irb"
2
+
3
+ # thank you!
4
+ # http://jameskilton.com/2009/04/02/embedding-irb-into-your-ruby-application/
5
+
6
+ module IRB # :nodoc:
7
+ # originally used the code snippet from the above link, which turned out to
8
+ # be a snippet from `ruby-debug'. However, I was having problems with CNTR-C
9
+ # and I realized I wanted to mimic irb, just be able to actually pass Irb.new
10
+ # a workspace param which it expected.
11
+ def self.start_session(ap_path = nil, binding = nil)
12
+ @@hack_binding = binding
13
+ IRB.start ap_path
14
+ end
15
+
16
+ def self.get_binding
17
+ @@hack_binding
18
+ end
19
+
20
+ class Irb
21
+ alias_method :org_initialize, :initialize
22
+ def initialize *args
23
+ workspace = args.shift
24
+ workspace ||= WorkSpace.new(IRB.get_binding)
25
+ args.unshift workspace
26
+ org_initialize *args
27
+ end
28
+ end
29
+ end
30
+
31
+ class Hudkins
32
+ module Command::Irb
33
+
34
+ ##
35
+ # In the irb console I want to access the same commands as
36
+ # Hudkins::Command::Exec but without the `run_' part.
37
+ def self.extend_object obj # :doc:
38
+ # in IRB allow run_* commands to be exec without `run_'
39
+ obj.command_list.each do |cmd|
40
+ s = <<-EOE
41
+ def self.#{cmd.gsub(/^run_/, '')} *args; #{cmd} *args; end
42
+ EOE
43
+ obj.module_eval s
44
+ end
45
+ super
46
+ end
47
+
48
+ ##
49
+ # This is cool. Override default method to add convenience methods for each
50
+ # job_name in #jobs. This allows `job_name' to be used as a method to
51
+ # access that particular job.
52
+ def method_missing sym, *args, &block
53
+ if job = @hud.jobs.find_by_name( sym )
54
+ job
55
+ else
56
+ super sym, *args, &block
57
+ end
58
+ end
59
+
60
+ ##
61
+ # help method in irb console
62
+ def hudkins
63
+ puts <<-EOS
64
+
65
+ self is Hudkins::Command
66
+ hudkins => This help message
67
+ reload! => reload lib
68
+ hud => Hudkins.new <hud_host>
69
+ job [job_name] => Hudkins::Job
70
+ job_name => Hudkins::Job
71
+ commands:
72
+ #{cmd_list.join("\n ")}
73
+
74
+ EOS
75
+ end
76
+
77
+ ##
78
+ # helper method in irb console to reload all the lib files.
79
+ def reload!
80
+ $".grep( /hudkins/ ).each do |f|
81
+ load f
82
+ end
83
+ nil
84
+ end
85
+ end # Command::Irb
86
+ end # Hudkins
@@ -0,0 +1,69 @@
1
+ class Hudkins
2
+ # common methods available for all classes
3
+ module Common
4
+
5
+ # hide instance variables
6
+ def inspect( string = nil )
7
+ new_string = " " << string if string
8
+ "#<%s:0x%x%s>" % [self.class, (self.object_id << 1), new_string]
9
+ end
10
+
11
+ ##
12
+ # === Description
13
+ # useful for escaping parameters to be sent to #get/#post
14
+ #
15
+ # === Parameters
16
+ # +Hash+:: converts a simple one dimentional hash to a url parameter
17
+ # query string and uri escapes it.
18
+ # +else+:: converts to string and uri escapes it.
19
+ def url_escape msg
20
+ case msg
21
+ when Hash then
22
+ URI.escape( msg.map {|k,v| "#{k}=#{v}" }.join("&") )
23
+ else
24
+ URI.escape( msg.to_s )
25
+ end
26
+ end
27
+
28
+ #--
29
+ # these classes are being included in the @hudkins class.
30
+ #++
31
+
32
+ def hudkins
33
+ @hudkins
34
+ end
35
+
36
+ ##
37
+ # accessor methods for child classes to get
38
+ def get *args
39
+ @hudkins.get *args
40
+ end
41
+
42
+ ##
43
+ # accessor methods for child classes to post
44
+ def post *args
45
+ @hudkins.post *args
46
+ end
47
+
48
+ ##
49
+ # TODO host is not universally accessible so this method might not belong here..
50
+ # === Notes
51
+ # My Hudson server is not publicially accessible so when I can't connect,
52
+ # Socket#getaddrinfo (RestClient) times out after more than 20 seconds! I
53
+ # created a healper class Hudkins::HostLookup to timeout Socket#getaddrinfo
54
+ # after Hudkins::HostLookup#TIMEOUT seconds (defaults to 2).
55
+ def host_available?
56
+ HostLookup.available? @host, @options[:host_timeout]
57
+ end
58
+
59
+ ##
60
+ # TODO host is not universally accessible so this method might not belong here..
61
+ # Raise TimeoutError unless hudson host is responding within specified number of seconds.
62
+ def check_host_availability
63
+ # host is URI.
64
+ msg = "`%s' did not respond within the set number of seconds." % [ host.host ]
65
+ raise TimeoutError, msg unless host_available?
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,7 @@
1
+ class Hudkins
2
+ class ArgumentError < ::ArgumentError
3
+ def << (msg)
4
+ self.message << "\n" + msg
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,168 @@
1
+ # === Description
2
+ # Primary class for interacting with a Hudson job
3
+ #
4
+ # === Examples
5
+ # hud = Hudkins.new "http://hudson.com"
6
+ # job = hud.jobs.find_by_name :job_name
7
+ #
8
+ # job.disabled? # => true
9
+ # job.disabled = false
10
+ # job.post_config!
11
+ #
12
+ # === attr_accessor_from_config methods
13
+ #
14
+ # I created custom attr_accessor like DSL methods that create accessor like
15
+ # methods for the Hudkins::Job object to easily interact with the xml config.
16
+ # Paradigm is to use method_name? for boolean values and method_name! for any
17
+ # methods that post updates to the server.
18
+ #
19
+ class Hudkins::Job
20
+ include Hudkins::Common
21
+ extend Hudkins::Mixin
22
+
23
+ include Comparable
24
+
25
+ attr_reader :name, :url, :color, :path, :config
26
+
27
+ #TODO figure out how to define a custom rdoc method to correctly document these
28
+
29
+ ##
30
+ # :attr_accessor: scm_url
31
+ attr_accessor_from_config :scm_url, "//scm//remote"
32
+ ##
33
+ # :attr_accessor: description
34
+ attr_accessor_from_config :description, "//project//descriptoin"
35
+ ##
36
+ # :attr_accessor: can_roam
37
+ attr_accessor_from_config :can_roam, "//project//canRoam", :bool
38
+ ##
39
+ # :attr_accessor: disabled
40
+ attr_accessor_from_config :disabled, "//project//disabled", :bool
41
+ ##
42
+ # :attr_accessor: blocked_by_upstream
43
+ attr_accessor_from_config :blocked_by_upstream, "//project//blockBuildWhenUpstreamBuilding", :bool
44
+ ##
45
+ # :attr_accessor: concurrent_builds
46
+ attr_accessor_from_config :concurrent_builds, "//project//concurrentBuild", :bool
47
+ attr_accessor_from_config :mail_recipients, "//publishers//recipients"
48
+ # this is really an array. I haven't added functionality for that yet
49
+ attr_accessor_from_config :shell_builders, "//builders/hudson.tasks.Shell", :array
50
+ ##
51
+ # :attr_accessor: :rotate_logs_num
52
+ # Number of builds to keep
53
+ # -1 == infinite
54
+ attr_accessor_from_config :rotate_logs_num, "//logRotator/numToKeep", :int
55
+ ##
56
+ # :attr_accessor: :rotate_logs_days
57
+ # Number of days to keep builds
58
+ # -1 == infinite
59
+ attr_accessor_from_config :rotate_logs_days, "//logRotator/daysToKeep", Integer
60
+
61
+ def initialize hudkins, data
62
+ @hudkins = hudkins
63
+ @name = data["name"]
64
+ @url = URI.parse data["url"]
65
+ @color = data["color"]
66
+ @path = @url.path
67
+ end
68
+
69
+ def inspect
70
+ super "@name=#{@name}"
71
+ end
72
+
73
+ def url
74
+ @url.to_s
75
+ end
76
+
77
+ # Enumerables/Comparables...
78
+ def <=> other
79
+ # TODO how do I implement jobs.sort(&:path) ?
80
+ self.name <=> other.name
81
+ end
82
+
83
+ ##
84
+ # === Description
85
+ # get the job's config
86
+ # takes string (xml) or other's config
87
+ def update_config config = nil
88
+ config = case config
89
+ when String then
90
+ hudkins.parse_string( config, :xml )
91
+ when Nokogiri::XML::Document, NilClass then
92
+ config
93
+ else
94
+ raise "unknown config type #{config.class}"
95
+ end
96
+ @config = config || hudkins.get_parsed( path + "/config.xml", :accept => "application/xml" )
97
+ end
98
+
99
+ ##
100
+ # === Description
101
+ # accessor for job's config. Initializes then caches. Use update_config if out of date.
102
+ def config
103
+ @config ||= update_config
104
+ end
105
+
106
+ ##
107
+ # === Description
108
+ # Post the job's config back to the server to update it.
109
+ def post_config!
110
+ post path + "/config.xml", @config
111
+ end
112
+
113
+ def build!
114
+ get path + "/build"
115
+ end
116
+
117
+ def delete!
118
+ post path + "/doDelete"
119
+ end
120
+
121
+ def recreate!
122
+ hudkins.add_job name, config
123
+ end
124
+
125
+ def disable!
126
+ post path + "/disable"
127
+ end
128
+
129
+ def enable!
130
+ post path + "/enable"
131
+ end
132
+
133
+ ##
134
+ # === Description
135
+ # The remote api allows for updating just the description. I had to tweak the
136
+ # name because I still wanted description to be an attr_accessor_from_config
137
+ #
138
+ # === Example
139
+ # job.quick_description! # => "this is the description for job"
140
+ # job.quick_description! = "this is the new desc." # => Response obj
141
+ def quick_description! msg = nil
142
+ # another yuck!
143
+ if msg
144
+ post path + "/description?" + url_escape(:description => msg)
145
+ else
146
+ get( path + "/description" ).body
147
+ end
148
+ end
149
+
150
+ ##
151
+ # === Description
152
+ # Copy job to new_job_name
153
+ #
154
+ # === Example
155
+ # new_job = job.copy "new-job-name"
156
+ # new_job.scm_url = "http://svn/new/job/path"
157
+ # new_job.post_config!
158
+ def copy new_job_name
159
+ # post /createItem?name=NEWJOBNAME&mode=copy&from=FROMJOBNAME
160
+ response = post "/createItem?" +
161
+ url_escape(:name => new_job_name, :mode => "copy", :from => name)
162
+ # return new job object
163
+ hudkins.update_jobs.find_by_name new_job_name if response.success?
164
+ end
165
+
166
+ # not sure if we want this.
167
+ # alias_method :dup, :copy
168
+ end