flapjack 0.6.39 → 0.6.40
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -2
- data/Gemfile +5 -1
- data/README.md +3 -2
- data/Rakefile +2 -1
- data/bin/flapjack +2 -2
- data/bin/flapjack-nagios-receiver +2 -8
- data/bin/flapjack-populator +11 -11
- data/etc/flapjack_config.yaml.example +28 -0
- data/features/steps/events_steps.rb +1 -1
- data/features/steps/notifications_steps.rb +7 -4
- data/features/support/env.rb +17 -6
- data/flapjack.gemspec +1 -0
- data/lib/flapjack/api.rb +72 -28
- data/lib/flapjack/configuration.rb +9 -1
- data/lib/flapjack/coordinator.rb +138 -162
- data/lib/flapjack/data/contact.rb +3 -1
- data/lib/flapjack/data/entity.rb +10 -1
- data/lib/flapjack/data/entity_check.rb +19 -21
- data/lib/flapjack/data/event.rb +26 -27
- data/lib/flapjack/data/message.rb +45 -0
- data/lib/flapjack/data/notification.rb +49 -0
- data/lib/flapjack/executive.rb +53 -74
- data/lib/flapjack/filters/acknowledgement.rb +14 -11
- data/lib/flapjack/jabber.rb +84 -18
- data/lib/flapjack/notification/email.rb +67 -37
- data/lib/flapjack/notification/sms.rb +40 -28
- data/lib/flapjack/oobetet.rb +1 -1
- data/lib/flapjack/pagerduty.rb +24 -15
- data/lib/flapjack/patches.rb +3 -1
- data/lib/flapjack/pikelet.rb +51 -20
- data/lib/flapjack/rack_logger.rb +8 -0
- data/lib/flapjack/version.rb +1 -1
- data/lib/flapjack/web.rb +51 -27
- data/spec/lib/flapjack/api_spec.rb +28 -3
- data/spec/lib/flapjack/coordinator_spec.rb +69 -43
- data/spec/lib/flapjack/data/contact_spec.rb +17 -9
- data/spec/lib/flapjack/data/entity_check_spec.rb +0 -25
- data/spec/lib/flapjack/data/entity_spec.rb +4 -0
- data/spec/lib/flapjack/data/global_spec.rb +6 -0
- data/spec/lib/flapjack/data/message_spec.rb +6 -0
- data/spec/lib/flapjack/data/notification_spec.rb +6 -0
- data/spec/lib/flapjack/executive_spec.rb +2 -2
- data/spec/lib/flapjack/jabber_spec.rb +8 -9
- data/spec/lib/flapjack/pagerduty_spec.rb +53 -45
- data/spec/lib/flapjack/utility_spec.rb +55 -0
- data/spec/lib/flapjack/web_spec.rb +7 -5
- data/tasks/events.rake +26 -59
- data/tasks/profile.rake +366 -0
- metadata +30 -19
- data/lib/flapjack/notification/common.rb +0 -23
- data/lib/flapjack/persistence/couch.rb +0 -5
- data/lib/flapjack/persistence/couch/connection.rb +0 -66
- data/lib/flapjack/persistence/couch/couch.rb +0 -63
- data/lib/flapjack/persistence/data_mapper.rb +0 -3
- data/lib/flapjack/persistence/data_mapper/data_mapper.rb +0 -67
- data/lib/flapjack/persistence/data_mapper/models/check.rb +0 -90
- data/lib/flapjack/persistence/data_mapper/models/check_template.rb +0 -20
- data/lib/flapjack/persistence/data_mapper/models/event.rb +0 -19
- data/lib/flapjack/persistence/data_mapper/models/node.rb +0 -18
- data/lib/flapjack/persistence/data_mapper/models/related_check.rb +0 -15
- data/lib/flapjack/persistence/sqlite3.rb +0 -3
- data/lib/flapjack/persistence/sqlite3/sqlite3.rb +0 -166
- data/lib/flapjack/transports/beanstalkd.rb +0 -50
- data/lib/flapjack/transports/result.rb +0 -58
- data/lib/flapjack/worker/application.rb +0 -121
- data/lib/flapjack/worker/cli.rb +0 -49
@@ -56,7 +56,15 @@ module Flapjack
|
|
56
56
|
config_env['redis'][k] = v
|
57
57
|
end
|
58
58
|
|
59
|
-
config_env
|
59
|
+
redis_path = (config_env['redis']['path'] || nil)
|
60
|
+
base_opts = {:db => (config_env['redis']['db'] || 0)}
|
61
|
+
redis_config = base_opts.merge(
|
62
|
+
(redis_path ? { :path => redis_path } :
|
63
|
+
{ :host => (config_env['redis']['host'] || '127.0.0.1'),
|
64
|
+
:port => (config_env['redis']['port'] || 6379)})
|
65
|
+
)
|
66
|
+
|
67
|
+
return config_env, redis_config
|
60
68
|
end
|
61
69
|
|
62
70
|
end
|
data/lib/flapjack/coordinator.rb
CHANGED
@@ -30,8 +30,9 @@ module Flapjack
|
|
30
30
|
|
31
31
|
include Flapjack::Daemonizable
|
32
32
|
|
33
|
-
def initialize(config
|
33
|
+
def initialize(config, redis_options)
|
34
34
|
@config = config
|
35
|
+
@redis_options = redis_options
|
35
36
|
@pikelets = []
|
36
37
|
|
37
38
|
@logger = Log4r::Logger.new("flapjack-coordinator")
|
@@ -42,59 +43,50 @@ module Flapjack
|
|
42
43
|
def start(options = {})
|
43
44
|
@signals = options[:signals]
|
44
45
|
if options[:daemonize]
|
46
|
+
@signals = options[:signals]
|
45
47
|
daemonize
|
46
48
|
else
|
47
|
-
|
49
|
+
run(:signals => options[:signals])
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
51
53
|
def after_daemonize
|
52
|
-
|
54
|
+
run(:signals => @signals)
|
53
55
|
end
|
54
56
|
|
55
57
|
def stop
|
58
|
+
return if @stopping
|
59
|
+
@stopping = true
|
56
60
|
shutdown
|
57
61
|
end
|
58
62
|
|
59
63
|
private
|
60
64
|
|
61
|
-
|
65
|
+
# map from config key to pikelet class
|
66
|
+
PIKELET_TYPES = {'executive' => Flapjack::Executive,
|
67
|
+
'jabber_gateway' => Flapjack::Jabber,
|
68
|
+
'pagerduty_gateway' => Flapjack::Pagerduty,
|
69
|
+
'oobetet' => Flapjack::Oobetet,
|
62
70
|
|
63
|
-
|
64
|
-
|
65
|
-
redis_host = @config['redis']['host'] || '127.0.0.1'
|
66
|
-
redis_port = @config['redis']['port'] || 6379
|
67
|
-
redis_path = @config['redis']['path'] || nil
|
68
|
-
redis_db = @config['redis']['db'] || 0
|
71
|
+
'web' => Flapjack::Web,
|
72
|
+
'api' => Flapjack::API,
|
69
73
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
74
|
+
'email_notifier' => Flapjack::Notification::Email,
|
75
|
+
'sms_notifier' => Flapjack::Notification::Sms}
|
76
|
+
|
77
|
+
def run(options = {})
|
75
78
|
|
76
79
|
EM.synchrony do
|
77
80
|
@logger.debug "config keys: #{@config.keys}"
|
78
81
|
|
79
|
-
pikelet_keys = ['executive', 'jabber_gateway', 'pagerduty_gateway',
|
80
|
-
'email_notifier', 'sms_notifier', 'web', 'api',
|
81
|
-
'oobetet']
|
82
|
-
|
83
82
|
@config.keys.each do |pikelet_type|
|
84
|
-
next unless
|
83
|
+
next unless PIKELET_TYPES.keys.include?(pikelet_type) &&
|
85
84
|
@config[pikelet_type].is_a?(Hash) &&
|
86
85
|
@config[pikelet_type]['enabled']
|
87
86
|
@logger.debug "coordinator is now initialising the #{pikelet_type} pikelet"
|
88
87
|
pikelet_cfg = @config[pikelet_type]
|
89
88
|
|
90
|
-
|
91
|
-
when 'executive', 'jabber_gateway', 'pagerduty_gateway', 'oobetet'
|
92
|
-
build_pikelet(pikelet_type, pikelet_cfg)
|
93
|
-
when 'web', 'api'
|
94
|
-
build_thin_pikelet(pikelet_type, pikelet_cfg)
|
95
|
-
when 'email_notifier', 'sms_notifier'
|
96
|
-
build_resque_pikelet(pikelet_type, pikelet_cfg)
|
97
|
-
end
|
89
|
+
build_pikelet(pikelet_type, pikelet_cfg)
|
98
90
|
end
|
99
91
|
|
100
92
|
setup_signals if @signals
|
@@ -102,186 +94,166 @@ module Flapjack
|
|
102
94
|
|
103
95
|
end
|
104
96
|
|
97
|
+
# the global nature of this seems at odds with it calling stop
|
98
|
+
# within a single coordinator instance. Coordinator is essentially
|
99
|
+
# a singleton anyway...
|
105
100
|
def setup_signals
|
106
|
-
trap('INT') { stop }
|
107
|
-
trap('TERM') { stop }
|
108
|
-
unless
|
109
|
-
trap('QUIT') { stop }
|
110
|
-
# trap('HUP') { }
|
101
|
+
Kernel.trap('INT') { stop }
|
102
|
+
Kernel.trap('TERM') { stop }
|
103
|
+
unless RbConfig::CONFIG['host_os'] =~ /mswin|windows|cygwin/i
|
104
|
+
Kernel.trap('QUIT') { stop }
|
105
|
+
# Kernel.trap('HUP') { }
|
111
106
|
end
|
112
107
|
end
|
113
108
|
|
114
109
|
def build_pikelet(pikelet_type, pikelet_cfg)
|
115
|
-
pikelet_class =
|
116
|
-
when 'executive'
|
117
|
-
Flapjack::Executive
|
118
|
-
when 'jabber_gateway'
|
119
|
-
Flapjack::Jabber
|
120
|
-
when 'pagerduty_gateway'
|
121
|
-
Flapjack::Pagerduty
|
122
|
-
when 'oobetet'
|
123
|
-
Flapjack::Oobetet
|
124
|
-
end
|
125
|
-
return unless pikelet_class
|
126
|
-
|
127
|
-
pikelet = pikelet_class.new
|
128
|
-
f = Fiber.new {
|
129
|
-
begin
|
130
|
-
pikelet.bootstrap(:redis => @redis_options, :config => pikelet_cfg)
|
131
|
-
pikelet.main
|
132
|
-
rescue Exception => e
|
133
|
-
trace = e.backtrace.join("\n")
|
134
|
-
@logger.fatal "#{e.message}\n#{trace}"
|
135
|
-
stop
|
136
|
-
end
|
137
|
-
}
|
138
|
-
@pikelets << {:fiber => f, :type => pikelet_type, :instance => pikelet}
|
139
|
-
f.resume
|
140
|
-
@logger.debug "new fiber created for #{pikelet_type}"
|
141
|
-
end
|
110
|
+
return unless pikelet_class = PIKELET_TYPES[pikelet_type]
|
142
111
|
|
143
|
-
|
144
|
-
|
145
|
-
when 'web'
|
146
|
-
Flapjack::Web
|
147
|
-
when 'api'
|
148
|
-
Flapjack::API
|
149
|
-
end
|
150
|
-
return unless pikelet_class
|
151
|
-
|
152
|
-
port = nil
|
153
|
-
if pikelet_cfg['port']
|
154
|
-
port = pikelet_cfg['port'].to_i
|
155
|
-
end
|
112
|
+
inc_mod = pikelet_class.included_modules
|
113
|
+
ext_mod = extended_modules(pikelet_class)
|
156
114
|
|
157
|
-
|
115
|
+
pikelet = nil
|
116
|
+
fiber = nil
|
158
117
|
|
159
|
-
|
160
|
-
|
118
|
+
if inc_mod.include?(Flapjack::GenericPikelet)
|
119
|
+
pikelet = pikelet_class.new
|
120
|
+
pikelet.bootstrap(:config => pikelet_cfg, :redis_config => @redis_options)
|
161
121
|
|
162
|
-
|
122
|
+
else
|
123
|
+
pikelet_class.bootstrap(:config => pikelet_cfg, :redis_config => @redis_options)
|
163
124
|
|
164
|
-
|
165
|
-
@pikelets << {:instance => pikelet, :type => pikelet_type}
|
166
|
-
pikelet.start
|
167
|
-
@logger.debug "new thin server instance started for #{pikelet_type}"
|
168
|
-
end
|
125
|
+
if ext_mod.include?(Flapjack::ThinPikelet)
|
169
126
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
when 'sms_notifier'
|
175
|
-
Flapjack::Notification::Sms
|
176
|
-
end
|
177
|
-
return unless pikelet_class
|
178
|
-
|
179
|
-
# set up connection pooling, stop resque errors (ensure that it's only
|
180
|
-
# done once)
|
181
|
-
@resque_pool = nil
|
182
|
-
if (['email_notifier', 'sms_notifier'] & @pikelets.collect {|p| p[:type]}).empty?
|
183
|
-
pool = Flapjack::RedisPool.new(:config => @redis_options)
|
184
|
-
::Resque.redis = pool
|
185
|
-
@resque_pool = pool
|
186
|
-
## NB: can override the default 'resque' namespace like this
|
187
|
-
#::Resque.redis.namespace = 'flapjack'
|
188
|
-
end
|
127
|
+
unless @thin_silenced
|
128
|
+
Thin::Logging.silent = true
|
129
|
+
@thin_silenced = true
|
130
|
+
end
|
189
131
|
|
190
|
-
|
191
|
-
|
192
|
-
|
132
|
+
pikelet = Thin::Server.new('0.0.0.0',
|
133
|
+
pikelet_class.instance_variable_get('@port'),
|
134
|
+
pikelet_class, :signals => false)
|
193
135
|
|
194
|
-
|
195
|
-
smtp_config = {}
|
136
|
+
elsif ext_mod.include?(Flapjack::ResquePikelet)
|
196
137
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
138
|
+
# set up connection pooling, stop resque errors
|
139
|
+
unless @resque_pool
|
140
|
+
@resque_pool = Flapjack::RedisPool.new(:config => @redis_options)
|
141
|
+
::Resque.redis = @resque_pool
|
142
|
+
## NB: can override the default 'resque' namespace like this
|
143
|
+
#::Resque.redis.namespace = 'flapjack'
|
201
144
|
end
|
145
|
+
|
146
|
+
# TODO error if pikelet_cfg['queue'].nil?
|
147
|
+
pikelet = EM::Resque::Worker.new(pikelet_cfg['queue'])
|
148
|
+
# # Use these to debug the resque workers
|
149
|
+
# pikelet.verbose = true
|
150
|
+
# pikelet.very_verbose = true
|
202
151
|
end
|
203
152
|
|
204
|
-
|
205
|
-
|
153
|
+
end
|
154
|
+
|
155
|
+
pikelet_info = {:class => pikelet_class, :instance => pikelet}
|
156
|
+
|
157
|
+
if inc_mod.include?(Flapjack::GenericPikelet) ||
|
158
|
+
ext_mod.include?(Flapjack::ResquePikelet)
|
159
|
+
|
160
|
+
fiber = Fiber.new {
|
161
|
+
begin
|
162
|
+
# Can't use local inc_mod/ext_mod variables in the new fiber
|
163
|
+
if pikelet.is_a?(Flapjack::GenericPikelet)
|
164
|
+
pikelet.main
|
165
|
+
elsif extended_modules(pikelet_class).include?(Flapjack::ResquePikelet)
|
166
|
+
pikelet.work(0.1)
|
167
|
+
end
|
168
|
+
rescue Exception => e
|
169
|
+
trace = e.backtrace.join("\n")
|
170
|
+
@logger.fatal "#{e.message}\n#{trace}"
|
171
|
+
stop
|
172
|
+
end
|
206
173
|
}
|
174
|
+
|
175
|
+
pikelet_info[:fiber] = fiber
|
176
|
+
fiber.resume
|
177
|
+
@logger.debug "new fiber created for #{pikelet_type}"
|
178
|
+
elsif ext_mod.include?(Flapjack::ThinPikelet)
|
179
|
+
pikelet.start
|
180
|
+
@logger.debug "new thin server instance started for #{pikelet_type}"
|
207
181
|
end
|
208
182
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
rescue Exception => e
|
221
|
-
trace = e.backtrace.join("\n")
|
222
|
-
@logger.fatal "#{e.message}\n#{trace}"
|
223
|
-
stop
|
183
|
+
@pikelets << pikelet_info
|
184
|
+
end
|
185
|
+
|
186
|
+
# only prints state changes, otherwise pikelets not closing promptly can
|
187
|
+
# cause everything else to be spammy
|
188
|
+
def health_check
|
189
|
+
@pikelets.each do |pik|
|
190
|
+
status = if extended_modules(pik[:class]).include?(Flapjack::ThinPikelet)
|
191
|
+
pik[:instance].backend.size > 0 ? 'running' : 'stopped'
|
192
|
+
elsif pik[:fiber]
|
193
|
+
pik[:fiber].alive? ? 'running' : 'stopped'
|
224
194
|
end
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
195
|
+
next if pik.has_key?(:status) && pik[:status].eql?(status)
|
196
|
+
@logger.info "#{pik[:class].name}: #{status}"
|
197
|
+
pik[:status] = status
|
198
|
+
end
|
229
199
|
end
|
230
200
|
|
231
|
-
# # TODO rewrite to be less spammy -- print only initial state and changes
|
232
|
-
# def health_check
|
233
|
-
# @pikelets.each do |pik|
|
234
|
-
# if pik[:instance].is_a?(Thin::Server)
|
235
|
-
# s = pik[:instance].backend.size
|
236
|
-
# @logger.debug "thin on port #{pik[:instance].port} - #{s} connections"
|
237
|
-
# elsif pik[:fiber]
|
238
|
-
# @logger.debug "#{pik[:type]}: #{pik[:fiber].alive? ? 'alive' : 'dead'}"
|
239
|
-
# end
|
240
|
-
# end
|
241
|
-
# end
|
242
|
-
|
243
|
-
# TODO whem merged with other changes, have this check pik[:class] instead,
|
244
|
-
# makes tests neater
|
245
201
|
def shutdown
|
246
202
|
@pikelets.each do |pik|
|
247
|
-
|
248
|
-
|
203
|
+
|
204
|
+
pik_inst = pik[:instance]
|
205
|
+
ext_mod = extended_modules(pik[:class])
|
206
|
+
|
207
|
+
# would be neater if we could use something similar for the class << self
|
208
|
+
# included pikelets as well
|
209
|
+
if pik_inst.is_a?(Flapjack::GenericPikelet)
|
249
210
|
if pik[:fiber] && pik[:fiber].alive?
|
250
|
-
|
211
|
+
pik_inst.stop
|
251
212
|
Fiber.new {
|
252
213
|
# this needs to use a separate Redis connection from the pikelet's
|
253
214
|
# one, as that's in the middle of its blpop
|
254
215
|
r = Redis.new(@redis_options.merge(:driver => 'synchrony'))
|
255
|
-
|
216
|
+
pik_inst.add_shutdown_event(:redis => r)
|
256
217
|
r.quit
|
257
218
|
}.resume
|
258
219
|
end
|
259
|
-
|
220
|
+
elsif ext_mod.include?(Flapjack::ResquePikelet)
|
260
221
|
# resque is polling, so we don't need a shutdown object
|
261
|
-
|
262
|
-
|
222
|
+
pik_inst.shutdown if pik[:fiber] && pik[:fiber].alive?
|
223
|
+
elsif ext_mod.include?(Flapjack::ThinPikelet)
|
263
224
|
# drop from this side, as HTTP keepalive etc. means browsers
|
264
225
|
# keep connections alive for ages, and we'd be hanging around
|
265
226
|
# waiting for them to drop
|
266
|
-
|
227
|
+
pik_inst.stop!
|
267
228
|
end
|
268
229
|
end
|
269
230
|
|
270
|
-
fibers = @pikelets.collect {|p| p[:fiber] }.compact
|
271
|
-
thin_pikelets = @pikelets.collect {|p| p[:instance]}.select {|i| i.is_a?(Thin::Server) }
|
272
|
-
|
273
231
|
Fiber.new {
|
232
|
+
|
274
233
|
loop do
|
275
|
-
|
276
|
-
|
234
|
+
health_check
|
235
|
+
|
236
|
+
if @pikelets.any? {|p| p[:status] == 'running'}
|
277
237
|
EM::Synchrony.sleep 0.25
|
278
238
|
else
|
279
239
|
@resque_pool.empty! if @resque_pool
|
280
240
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
241
|
+
@pikelets.each do |pik|
|
242
|
+
|
243
|
+
pik_inst = pik[:instance]
|
244
|
+
ext_mod = extended_modules(pik[:class])
|
245
|
+
|
246
|
+
if pik_inst.is_a?(Flapjack::GenericPikelet)
|
247
|
+
|
248
|
+
pik_inst.cleanup
|
249
|
+
|
250
|
+
elsif [Flapjack::ResquePikelet, Flapjack::ThinPikelet].any?{|fp|
|
251
|
+
ext_mod.include?(fp)
|
252
|
+
}
|
253
|
+
|
254
|
+
pik[:class].cleanup
|
255
|
+
|
256
|
+
end
|
285
257
|
end
|
286
258
|
|
287
259
|
EM.stop
|
@@ -291,6 +263,10 @@ module Flapjack
|
|
291
263
|
}.resume
|
292
264
|
end
|
293
265
|
|
266
|
+
def extended_modules(klass)
|
267
|
+
(class << klass; self; end).included_modules
|
268
|
+
end
|
269
|
+
|
294
270
|
end
|
295
271
|
|
296
272
|
end
|
@@ -24,7 +24,7 @@ module Flapjack
|
|
24
24
|
contact = self.find_by_id(id, :redis => redis)
|
25
25
|
ret << contact if contact
|
26
26
|
ret
|
27
|
-
}
|
27
|
+
}.sort_by {|c| [c.last_name, c.first_name]}
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.delete_all(options = {})
|
@@ -41,6 +41,8 @@ module Flapjack
|
|
41
41
|
raise "No id value passed" unless id
|
42
42
|
logger = options[:logger]
|
43
43
|
|
44
|
+
return unless redis.hexists("contact:#{id}", 'first_name')
|
45
|
+
|
44
46
|
fn, ln, em = redis.hmget("contact:#{id}", 'first_name', 'last_name', 'email')
|
45
47
|
me = redis.hgetall("contact_media:#{id}")
|
46
48
|
|
data/lib/flapjack/data/entity.rb
CHANGED
@@ -13,7 +13,7 @@ module Flapjack
|
|
13
13
|
redis.keys("entity_id:*").collect {|k|
|
14
14
|
k =~ /^entity_id:(.+)$/; entity_name = $1
|
15
15
|
self.new(:name => entity_name, :id => redis.get("entity_id:#{entity_name}"), :redis => redis)
|
16
|
-
}
|
16
|
+
}.sort_by(&:name)
|
17
17
|
end
|
18
18
|
|
19
19
|
# NB: should probably be called in the context of a Redis multi block; not doing so
|
@@ -62,6 +62,15 @@ module Flapjack
|
|
62
62
|
self.new(:name => entity_name, :id => entity_id, :redis => redis)
|
63
63
|
end
|
64
64
|
|
65
|
+
def self.find_all_name_matching(pattern, options = {})
|
66
|
+
raise "Redis connection not set" unless redis = options[:redis]
|
67
|
+
matched_entities = redis.keys('check:*').collect {|check|
|
68
|
+
a, entity, c = check.split(':')
|
69
|
+
match = (entity =~ /#{pattern}/) ? entity : nil
|
70
|
+
}
|
71
|
+
matched_entities.compact.sort.uniq
|
72
|
+
end
|
73
|
+
|
65
74
|
def check_list
|
66
75
|
@redis.keys("check:#{@name}:*").map {|k| k =~ /^check:#{@name}:(.+)$/; $1}
|
67
76
|
end
|