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.
- data/CHANGES +159 -129
- data/LICENSE +21 -21
- data/README +75 -75
- data/Rakefile +161 -159
- data/bin/cerberus +2 -2
- data/lib/cerberus/builder/maven2.rb +38 -38
- data/lib/cerberus/builder/rake.rb +7 -7
- data/lib/cerberus/builder/rant.rb +7 -7
- data/lib/cerberus/builder/rspec.rb +13 -0
- data/lib/cerberus/builder/ruby_base.rb +48 -47
- data/lib/cerberus/cli.rb +73 -70
- data/lib/cerberus/component_lazy_loader.rb +2 -0
- data/lib/cerberus/config.example.yml +28 -28
- data/lib/cerberus/config.rb +47 -46
- data/lib/cerberus/constants.rb +8 -8
- data/lib/cerberus/latch.rb +26 -26
- data/lib/cerberus/manager.rb +296 -267
- data/lib/cerberus/publisher/base.rb +47 -47
- data/lib/cerberus/publisher/gmailer.rb +17 -0
- data/lib/cerberus/publisher/irc.rb +27 -27
- data/lib/cerberus/publisher/jabber.rb +25 -25
- data/lib/cerberus/publisher/mail.rb +36 -36
- data/lib/cerberus/publisher/netsmtp_tls_fix.rb +66 -66
- data/lib/cerberus/publisher/rss.rb +27 -28
- data/lib/cerberus/scm/cvs.rb +48 -48
- data/lib/cerberus/scm/darcs.rb +70 -70
- data/lib/cerberus/scm/svn.rb +83 -83
- data/lib/cerberus/utils.rb +156 -156
- data/test/config_test.rb +45 -45
- data/test/functional_test.rb +288 -287
- data/test/integration_test.rb +104 -104
- data/test/irc_publisher_test.rb +18 -18
- data/test/jabber_publisher_test.rb +21 -21
- data/test/mail_publisher_test.rb +25 -25
- data/test/maven2_builer_test.rb +81 -81
- data/test/mock/irc.rb +20 -20
- data/test/mock/manager.rb +10 -10
- data/test/mock/xmpp4r.rb +19 -19
- data/test/rss_publisher_test.rb +21 -21
- data/test/test_helper.rb +105 -105
- metadata +58 -40
- data/lib/cerberus/helper/xchar.rb +0 -61
data/lib/cerberus/manager.rb
CHANGED
@@ -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
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
class
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
else
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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
|