cerberus 0.3.5 → 0.3.6

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 (42) hide show
  1. data/CHANGES +159 -129
  2. data/LICENSE +21 -21
  3. data/README +75 -75
  4. data/Rakefile +161 -159
  5. data/bin/cerberus +2 -2
  6. data/lib/cerberus/builder/maven2.rb +38 -38
  7. data/lib/cerberus/builder/rake.rb +7 -7
  8. data/lib/cerberus/builder/rant.rb +7 -7
  9. data/lib/cerberus/builder/rspec.rb +13 -0
  10. data/lib/cerberus/builder/ruby_base.rb +48 -47
  11. data/lib/cerberus/cli.rb +73 -70
  12. data/lib/cerberus/component_lazy_loader.rb +2 -0
  13. data/lib/cerberus/config.example.yml +28 -28
  14. data/lib/cerberus/config.rb +47 -46
  15. data/lib/cerberus/constants.rb +8 -8
  16. data/lib/cerberus/latch.rb +26 -26
  17. data/lib/cerberus/manager.rb +296 -267
  18. data/lib/cerberus/publisher/base.rb +47 -47
  19. data/lib/cerberus/publisher/gmailer.rb +17 -0
  20. data/lib/cerberus/publisher/irc.rb +27 -27
  21. data/lib/cerberus/publisher/jabber.rb +25 -25
  22. data/lib/cerberus/publisher/mail.rb +36 -36
  23. data/lib/cerberus/publisher/netsmtp_tls_fix.rb +66 -66
  24. data/lib/cerberus/publisher/rss.rb +27 -28
  25. data/lib/cerberus/scm/cvs.rb +48 -48
  26. data/lib/cerberus/scm/darcs.rb +70 -70
  27. data/lib/cerberus/scm/svn.rb +83 -83
  28. data/lib/cerberus/utils.rb +156 -156
  29. data/test/config_test.rb +45 -45
  30. data/test/functional_test.rb +288 -287
  31. data/test/integration_test.rb +104 -104
  32. data/test/irc_publisher_test.rb +18 -18
  33. data/test/jabber_publisher_test.rb +21 -21
  34. data/test/mail_publisher_test.rb +25 -25
  35. data/test/maven2_builer_test.rb +81 -81
  36. data/test/mock/irc.rb +20 -20
  37. data/test/mock/manager.rb +10 -10
  38. data/test/mock/xmpp4r.rb +19 -19
  39. data/test/rss_publisher_test.rb +21 -21
  40. data/test/test_helper.rb +105 -105
  41. metadata +58 -40
  42. data/lib/cerberus/helper/xchar.rb +0 -61
@@ -1,268 +1,297 @@
1
- require 'fileutils'
2
-
3
- require 'cerberus/utils'
4
- require 'cerberus/constants'
5
- require 'cerberus/config'
6
- require 'cerberus/latch'
7
- require 'cerberus/component_lazy_loader'
8
-
9
- module Cerberus
10
- class AddCommand
11
- EXAMPLE_CONFIG = File.expand_path(File.dirname(__FILE__) + '/config.example.yml')
12
-
13
- def initialize(path, cli_options = {})
14
- @path, @cli_options = path, HashWithIndifferentAccess.new(cli_options)
15
- end
16
-
17
- def run
18
- scm_type = @cli_options[:scm] || Cerberus::SCM.guess_type(@path) || 'svn'
19
- scm = Cerberus::SCM.get(scm_type).new(@path, Config.new(nil, @cli_options))
20
- say "Can't find any #{scm_type} application under #{@path}" unless scm.url
21
-
22
- application_name = @cli_options[:application_name] || extract_project_name(@path)
23
-
24
- create_example_config
25
-
26
- config_name = "#{HOME}/config/#{application_name}.yml"
27
- say "Application #{application_name} already present in Cerberus" if File.exists?(config_name)
28
-
29
- app_config = { 'scm' => {
30
- 'url' => scm.url,
31
- 'type' => scm_type },
32
- 'publisher' => {'mail' => {'recipients' => @cli_options[:recipients]}}
33
- }
34
- app_config['builder'] = {'type' => @cli_options[:builder]} if @cli_options[:builder]
35
- dump_yml(config_name, app_config)
36
- puts "Application '#{application_name}' has been added to Cerberus successfully" unless @cli_options[:quiet]
37
- end
38
-
39
- private
40
- def extract_project_name(path)
41
- path = File.expand_path(path) if test(?d, path)
42
- File.basename(path).strip
43
- end
44
-
45
- def create_example_config
46
- FileUtils.mkpath(HOME) unless test(?d, HOME)
47
- FileUtils.cp(EXAMPLE_CONFIG, CONFIG_FILE) unless test(?f, CONFIG_FILE)
48
- end
49
- end
50
-
51
- class BuildCommand
52
- attr_reader :builder, :success, :scm, :status
53
-
54
- DEFAULT_CONFIG = {:scm => {:type => 'svn'},
55
- :log => {:enable => true}
56
- }
57
-
58
- def initialize(application_name, cli_options = {})
59
- unless File.exists?("#{HOME}/config/#{application_name}.yml")
60
- say "Project '#{application_name}' does not present in Cerberus. Type 'cerberus list' to see the list of all active projects."
61
- end
62
-
63
- app_root = "#{HOME}/work/#{application_name}"
64
-
65
- def_options = {:application_root => app_root + '/sources', :application_name => application_name} #pseudo options that stored in config. Could not be set in any config file not through CLI
66
- @config = Config.new(application_name, cli_options.merge(def_options))
67
- @config.merge!(DEFAULT_CONFIG, false)
68
-
69
- @status = Status.read("#{app_root}/status.log")
70
-
71
- scm_type = @config[:scm, :type]
72
- @scm = SCM.get(scm_type).new(@config[:application_root], @config)
73
- say "Client for SCM '#{scm_type}' does not installed" unless @scm.installed?
74
-
75
- builder_type = get_configuration_option(@config[:builder], :type, :rake)
76
- @builder = Builder.get(builder_type).new(@config)
77
- end
78
-
79
- def run
80
- begin
81
- Latch.lock("#{HOME}/work/#{@config[:application_name]}/.lock", :lock_ttl => 2 * LOCK_WAIT) do
82
- @scm.update!
83
-
84
- if @scm.has_changes? or @config[:force] or @status.previous_build_successful.nil?
85
- build_successful = @builder.run
86
- @status.keep(build_successful, @scm.current_revision, @builder.brokeness)
87
-
88
- #Save logs to directory
89
- if @config[:log, :enable]
90
- log_dir = "#{HOME}/work/#{@config[:application_name]}/logs/"
91
- FileUtils.mkpath(log_dir)
92
-
93
- time = Time.now.strftime("%Y%m%d%H%M%S")
94
- file_name = "#{log_dir}/#{time}-#{@status.current_state.to_s}.log"
95
- body = [ scm.last_commit_message, builder.output ].join("\n\n")
96
- IO.write(file_name, body)
97
- end
98
-
99
- #send notifications
100
- active_publishers = get_configuration_option(@config[:publisher], :active, 'mail')
101
- active_publishers.split(/\W+/).each do |pub|
102
-
103
- publisher_config = @config[:publisher, pub]
104
- raise "Publisher have no configuration: #{pub}" unless publisher_config
105
-
106
- events = interpret_state(publisher_config[:on_event] || @config[:publisher, :on_event] || 'default')
107
- Publisher.get(pub).publish(@status, self, @config) if events.include?(@status.current_state)
108
- end
109
-
110
-
111
- #Process hooks
112
- hooks = @config[:hook]
113
- hooks.each_pair{|name, hook|
114
- events = interpret_state(hook[:on_event] || 'all', false)
115
- if events.include?(@status.current_state)
116
- `#{hook[:action]}`
117
- end
118
- } if hooks
119
- end
120
-
121
- end #lock
122
- rescue Exception => e
123
- if ENV['CERBERUS_ENV'] == 'TEST'
124
- raise e
125
- else
126
- File.open("#{HOME}/error.log", File::WRONLY|File::APPEND|File::CREAT) do |f|
127
- f.puts Time.now.strftime("%a, %d %b %Y %H:%M:%S [#{@config[:application_name]}] -- #{e.class}")
128
- f.puts e.message unless e.message.empty?
129
- f.puts e.backtrace.collect{|line| ' '*5 + line}
130
- f.puts "\n"
131
- end
132
- end
133
- end
134
- end
135
-
136
- private
137
- def get_configuration_option(hash, defining_key = nil, default_option = nil)
138
- if hash
139
- return hash[defining_key] if hash[defining_key]
140
- return hash.keys[0] if hash.size == 1
141
- end
142
- return default_option
143
- end
144
- end
145
-
146
- class BuildAllCommand
147
- def initialize(cli_options = {})
148
- @cli_options = cli_options
149
- end
150
-
151
- def run
152
- threads = []
153
- Dir["#{HOME}/config/*.yml"].each do |fn|
154
- fn =~ %r{#{HOME}/config/(.*).yml}
155
- application_name = $1
156
-
157
- command = Cerberus::BuildCommand.new(application_name, @cli_options)
158
- threads << Thread.new { command.run }
159
- end
160
-
161
- @already_waited = false
162
- threads.each do |t|
163
- if @already_waited or not t.join(LOCK_WAIT)
164
- t.kill
165
- @already_waited = true
166
- end
167
- end
168
- end
169
- end
170
-
171
- class ListCommand
172
- def initialize(cli_options = {})
173
- end
174
-
175
- def run
176
- projects = Dir["#{HOME}/config/*.yml"]
177
- if projects.empty?
178
- puts "There are no any active projects"
179
- else
180
- puts "List of active projects:"
181
-
182
- projects.each do |fn|
183
- fn =~ %r{#{HOME}/config/(.*).yml}
184
-
185
- puts " * #{$1}"
186
- end
187
-
188
- puts "\nType 'cerberus build PROJECT_NAME' to build any of these projects"
189
- end
190
- end
191
- end
192
-
193
- #Fields that are contained in status file
194
- # successful (true mean previous build was successful, otherwise - false)
195
- # timestamp
196
- # revision
197
- # brokeness
198
- # successful_build_timestamp
199
- # successful_build_revision
200
- class Status
201
- attr_reader :previous_build_successful, :previous_brokeness, :current_build_successful, :current_brokeness
202
-
203
- def initialize(param)
204
- if param.is_a? Hash
205
- @hash = param
206
- @current_build_successful = @hash['state']
207
- @already_kept = true
208
- else
209
- @path = param
210
- value = File.exists?(@path) ? YAML.load(IO.read(@path)) : nil
211
-
212
- @hash =
213
- case value
214
- when String
215
- value = %w(succesful successful setup).include?(value) #fix typo in status values
216
- {'successful' => value}
217
- when nil
218
- {}
219
- else
220
- value
221
- end
222
-
223
- @already_kept = false
224
- end
225
-
226
- @previous_build_successful = @hash['successful']
227
- @previous_brokeness = @hash['brokeness']
228
- end
229
-
230
- def self.read(file_name)
231
- Status.new(file_name)
232
- end
233
-
234
- def keep(build_successful, revision, brokeness)
235
- raise 'Status could be kept only once. Please try to reread status file.' if @already_kept
236
-
237
- @current_brokeness = brokeness
238
- @current_build_successful = build_successful
239
-
240
- hash = {'successful' => @current_build_successful, 'timestamp' => Time.now, 'revision' => revision, 'brokeness' => brokeness}
241
- if build_successful
242
- hash['successful_build_timestamp'] = Time.now
243
- hash['successful_build_revision'] = revision
244
- else
245
- hash['successful_build_timestamp'] = @hash['successful_build_timestamp']
246
- hash['successful_build_revision'] = @hash['successful_build_revision']
247
- end
248
-
249
- File.open(@path, "w+", 0777) { |file| file.write(YAML.dump(hash)) }
250
-
251
- @already_kept = true
252
- end
253
-
254
- def current_state
255
- raise "Invalid project state. Before calculating status please do keeping of it." unless @already_kept
256
-
257
- if @current_build_successful
258
- if @previous_build_successful.nil?
259
- :setup
260
- else
261
- @previous_build_successful ? :successful : :revival
262
- end
263
- else
264
- @previous_build_successful ? :failed : :broken
265
- end
266
- end
267
- end
1
+ require 'fileutils'
2
+
3
+ require 'cerberus/utils'
4
+ require 'cerberus/constants'
5
+ require 'cerberus/config'
6
+ require 'cerberus/latch'
7
+ require 'cerberus/component_lazy_loader'
8
+
9
+ module Cerberus
10
+ class AddCommand
11
+ EXAMPLE_CONFIG = File.expand_path(File.dirname(__FILE__) + '/config.example.yml')
12
+
13
+ def initialize(path, cli_options = {})
14
+ @path, @cli_options = path, HashWithIndifferentAccess.new(cli_options)
15
+ end
16
+
17
+ def run
18
+ scm_type = @cli_options[:scm] || Cerberus::SCM.guess_type(@path) || 'svn'
19
+ scm = Cerberus::SCM.get(scm_type).new(@path, Config.new(nil, @cli_options))
20
+ say "Can't find any #{scm_type} application under #{@path}" unless scm.url
21
+
22
+ application_name = @cli_options[:application_name] || extract_project_name(@path)
23
+
24
+ create_example_config
25
+
26
+ config_name = "#{HOME}/config/#{application_name}.yml"
27
+ say "Application #{application_name} already present in Cerberus" if File.exists?(config_name)
28
+
29
+ app_config = { 'scm' => {
30
+ 'url' => scm.url,
31
+ 'type' => scm_type },
32
+ 'publisher' => {'mail' => {'recipients' => @cli_options[:recipients]}}
33
+ }
34
+ app_config['builder'] = {'type' => @cli_options[:builder]} if @cli_options[:builder]
35
+ dump_yml(config_name, app_config)
36
+ puts "Application '#{application_name}' has been added to Cerberus successfully" unless @cli_options[:quiet]
37
+ end
38
+
39
+ private
40
+ def extract_project_name(path)
41
+ path = File.expand_path(path) if test(?d, path)
42
+ File.basename(path).strip
43
+ end
44
+
45
+ def create_example_config
46
+ FileUtils.mkpath(HOME) unless test(?d, HOME)
47
+ FileUtils.cp(EXAMPLE_CONFIG, CONFIG_FILE) unless test(?f, CONFIG_FILE)
48
+ end
49
+ end
50
+
51
+ class RemoveCommand
52
+ # DRY: this is ugly and needs refactoring. It duplicates functionality from other
53
+ # classes a lot.
54
+
55
+ def initialize(application_name, cli_options = {})
56
+ unless File.exists?("#{HOME}/config/#{application_name}.yml")
57
+ say "Project '#{application_name}' does not present in Cerberus. Type 'cerberus list' to see the list of all active projects."
58
+ end
59
+ @app_root = "#{HOME}/work/#{application_name}"
60
+
61
+ def_options = {:application_root => @app_root, :application_name => application_name}
62
+ @config = Config.new(application_name, cli_options.merge(def_options))
63
+ end
64
+
65
+ def run
66
+ application_name = @config[:application_name]
67
+
68
+ config_name = "#{HOME}/config/#{application_name}.yml"
69
+
70
+ if not File.exists?(config_name)
71
+ say "Unknown application #{application_name}"
72
+ exit(1)
73
+ end
74
+ FileUtils.rm_rf @config[:application_root]
75
+ File.unlink config_name
76
+ puts "Application '#{application_name}' removed." unless @config[:quiet]
77
+ end
78
+ end
79
+
80
+ class BuildCommand
81
+ attr_reader :builder, :success, :scm, :status
82
+
83
+ DEFAULT_CONFIG = {:scm => {:type => 'svn'},
84
+ :log => {:enable => true}
85
+ }
86
+
87
+ def initialize(application_name, cli_options = {})
88
+ unless File.exists?("#{HOME}/config/#{application_name}.yml")
89
+ say "Project '#{application_name}' does not present in Cerberus. Type 'cerberus list' to see the list of all active projects."
90
+ end
91
+
92
+ app_root = "#{HOME}/work/#{application_name}"
93
+
94
+ def_options = {:application_root => app_root + '/sources', :application_name => application_name} #pseudo options that stored in config. Could not be set in any config file not through CLI
95
+ @config = Config.new(application_name, cli_options.merge(def_options))
96
+ @config.merge!(DEFAULT_CONFIG, false)
97
+
98
+ @status = Status.read("#{app_root}/status.log")
99
+
100
+ scm_type = @config[:scm, :type]
101
+ @scm = SCM.get(scm_type).new(@config[:application_root], @config)
102
+ say "Client for SCM '#{scm_type}' does not installed" unless @scm.installed?
103
+
104
+ builder_type = get_configuration_option(@config[:builder], :type, :rake)
105
+ @builder = Builder.get(builder_type).new(@config)
106
+ end
107
+
108
+ def run
109
+ begin
110
+ Latch.lock("#{HOME}/work/#{@config[:application_name]}/.lock", :lock_ttl => 2 * LOCK_WAIT) do
111
+ @scm.update!
112
+
113
+ if @scm.has_changes? or @config[:force] or @status.previous_build_successful.nil?
114
+ build_successful = @builder.run
115
+ @status.keep(build_successful, @scm.current_revision, @builder.brokeness)
116
+
117
+ #Save logs to directory
118
+ if @config[:log, :enable]
119
+ log_dir = "#{HOME}/work/#{@config[:application_name]}/logs/"
120
+ FileUtils.mkpath(log_dir)
121
+
122
+ time = Time.now.strftime("%Y%m%d%H%M%S")
123
+ file_name = "#{log_dir}/#{time}-#{@status.current_state.to_s}.log"
124
+ body = [ scm.last_commit_message, builder.output ].join("\n\n")
125
+ IO.write(file_name, body)
126
+ end
127
+
128
+ #send notifications
129
+ active_publishers = get_configuration_option(@config[:publisher], :active, 'mail')
130
+ active_publishers.split(/\W+/).each do |pub|
131
+
132
+ publisher_config = @config[:publisher, pub]
133
+ raise "Publisher have no configuration: #{pub}" unless publisher_config
134
+
135
+ events = interpret_state(publisher_config[:on_event] || @config[:publisher, :on_event] || 'default')
136
+ Publisher.get(pub).publish(@status, self, @config) if events.include?(@status.current_state)
137
+ end
138
+
139
+
140
+ #Process hooks
141
+ hooks = @config[:hook]
142
+ hooks.each_pair{|name, hook|
143
+ events = interpret_state(hook[:on_event] || 'all', false)
144
+ if events.include?(@status.current_state)
145
+ `#{hook[:action]}`
146
+ end
147
+ } if hooks
148
+ end
149
+
150
+ end #lock
151
+ rescue Exception => e
152
+ if ENV['CERBERUS_ENV'] == 'TEST'
153
+ raise e
154
+ else
155
+ File.open("#{HOME}/error.log", File::WRONLY|File::APPEND|File::CREAT) do |f|
156
+ f.puts Time.now.strftime("%a, %d %b %Y %H:%M:%S [#{@config[:application_name]}] -- #{e.class}")
157
+ f.puts e.message unless e.message.empty?
158
+ f.puts e.backtrace.collect{|line| ' '*5 + line}
159
+ f.puts "\n"
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ private
166
+ def get_configuration_option(hash, defining_key = nil, default_option = nil)
167
+ if hash
168
+ return hash[defining_key] if hash[defining_key]
169
+ return hash.keys[0] if hash.size == 1
170
+ end
171
+ return default_option
172
+ end
173
+ end
174
+
175
+ class BuildAllCommand
176
+ def initialize(cli_options = {})
177
+ @cli_options = cli_options
178
+ end
179
+
180
+ def run
181
+ threads = []
182
+ Dir["#{HOME}/config/*.yml"].each do |fn|
183
+ fn =~ %r{#{HOME}/config/(.*).yml}
184
+ application_name = $1
185
+
186
+ command = Cerberus::BuildCommand.new(application_name, @cli_options)
187
+ threads << Thread.new { command.run }
188
+ end
189
+
190
+ @already_waited = false
191
+ threads.each do |t|
192
+ if @already_waited or not t.join(LOCK_WAIT)
193
+ t.kill
194
+ @already_waited = true
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ class ListCommand
201
+ def initialize(cli_options = {})
202
+ end
203
+
204
+ def run
205
+ projects = Dir["#{HOME}/config/*.yml"]
206
+ if projects.empty?
207
+ puts "There are no any active projects"
208
+ else
209
+ puts "List of active projects:"
210
+
211
+ projects.each do |fn|
212
+ fn =~ %r{#{HOME}/config/(.*).yml}
213
+
214
+ puts " * #{$1}"
215
+ end
216
+
217
+ puts "\nType 'cerberus build PROJECT_NAME' to build any of these projects"
218
+ end
219
+ end
220
+ end
221
+
222
+ #Fields that are contained in status file
223
+ # successful (true mean previous build was successful, otherwise - false)
224
+ # timestamp
225
+ # revision
226
+ # brokeness
227
+ # successful_build_timestamp
228
+ # successful_build_revision
229
+ class Status
230
+ attr_reader :previous_build_successful, :previous_brokeness, :current_build_successful, :current_brokeness
231
+
232
+ def initialize(param)
233
+ if param.is_a? Hash
234
+ @hash = param
235
+ @current_build_successful = @hash['state']
236
+ @already_kept = true
237
+ else
238
+ @path = param
239
+ value = File.exists?(@path) ? YAML.load(IO.read(@path)) : nil
240
+
241
+ @hash =
242
+ case value
243
+ when String
244
+ value = %w(succesful successful setup).include?(value) #fix typo in status values
245
+ {'successful' => value}
246
+ when nil
247
+ {}
248
+ else
249
+ value
250
+ end
251
+
252
+ @already_kept = false
253
+ end
254
+
255
+ @previous_build_successful = @hash['successful']
256
+ @previous_brokeness = @hash['brokeness']
257
+ end
258
+
259
+ def self.read(file_name)
260
+ Status.new(file_name)
261
+ end
262
+
263
+ def keep(build_successful, revision, brokeness)
264
+ raise 'Status could be kept only once. Please try to reread status file.' if @already_kept
265
+
266
+ @current_brokeness = brokeness
267
+ @current_build_successful = build_successful
268
+
269
+ hash = {'successful' => @current_build_successful, 'timestamp' => Time.now, 'revision' => revision, 'brokeness' => brokeness}
270
+ if build_successful
271
+ hash['successful_build_timestamp'] = Time.now
272
+ hash['successful_build_revision'] = revision
273
+ else
274
+ hash['successful_build_timestamp'] = @hash['successful_build_timestamp']
275
+ hash['successful_build_revision'] = @hash['successful_build_revision']
276
+ end
277
+
278
+ File.open(@path, "w+", 0777) { |file| file.write(YAML.dump(hash)) }
279
+
280
+ @already_kept = true
281
+ end
282
+
283
+ def current_state
284
+ raise "Invalid project state. Before calculating status please do keeping of it." unless @already_kept
285
+
286
+ if @current_build_successful
287
+ if @previous_build_successful.nil?
288
+ :setup
289
+ else
290
+ @previous_build_successful ? :successful : :revival
291
+ end
292
+ else
293
+ @previous_build_successful ? :failed : :broken
294
+ end
295
+ end
296
+ end
268
297
  end