sunshine 1.2.0 → 1.2.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.
@@ -11,6 +11,10 @@ module Sunshine
11
11
 
12
12
  class Daemon
13
13
 
14
+ START_FAILED_CODE = 10
15
+ STOP_FAILED_CODE = 11
16
+ RESTART_FAILED_CODE = 12
17
+ STATUS_DOWN_CODE = 13
14
18
 
15
19
  ##
16
20
  # Returns an array of method names to assign to the binder
@@ -142,7 +146,7 @@ module Sunshine
142
146
  binder = config_binding server_app.shell
143
147
 
144
148
  configure_remote_dirs server_app.shell
145
- touch_log_files server_app.shell
149
+ chown_log_files server_app.shell
146
150
 
147
151
  yield(server_app, binder) if block_given?
148
152
 
@@ -156,7 +160,7 @@ module Sunshine
156
160
  @setup_successful = true
157
161
 
158
162
  rescue => e
159
- raise CriticalDeployError.new(e, "Could not setup #{@name}")
163
+ raise DaemonError.new(e, "Could not setup #{@name}")
160
164
  end
161
165
 
162
166
 
@@ -186,12 +190,12 @@ module Sunshine
186
190
 
187
191
  each_server_app do |server_app|
188
192
  begin
189
- server_app.shell.call start_cmd,
193
+ server_app.shell.call _start_cmd,
190
194
  :sudo => pick_sudo(server_app.shell)
191
195
 
192
196
  yield(server_app) if block_given?
193
197
  rescue => e
194
- raise CriticalDeployError.new(e, "Could not start #{@name}")
198
+ raise DaemonError.new(e, "Could not start #{@name}")
195
199
  end
196
200
  end
197
201
  end
@@ -203,7 +207,7 @@ module Sunshine
203
207
 
204
208
  def status
205
209
  each_server_app do |server_app|
206
- server_app.shell.call status_cmd, :sudo => pick_sudo(server_app.shell)
210
+ server_app.shell.call _status_cmd, :sudo => pick_sudo(server_app.shell)
207
211
  end
208
212
  true
209
213
 
@@ -220,12 +224,12 @@ module Sunshine
220
224
 
221
225
  each_server_app do |server_app|
222
226
  begin
223
- server_app.shell.call stop_cmd,
227
+ server_app.shell.call _stop_cmd,
224
228
  :sudo => pick_sudo(server_app.shell)
225
229
 
226
230
  yield(server_app) if block_given?
227
231
  rescue => e
228
- raise CriticalDeployError.new(e, "Could not stop #{@name}")
232
+ raise DaemonError.new(e, "Could not stop #{@name}")
229
233
  end
230
234
  end
231
235
  end
@@ -242,25 +246,42 @@ module Sunshine
242
246
  Sunshine.logger.info @name, "Restarting #{@name} daemon" do
243
247
  each_server_app do |server_app|
244
248
  begin
245
- server_app.shell.call restart_cmd,
249
+ server_app.shell.call _restart_cmd,
246
250
  :sudo => pick_sudo(server_app.shell)
247
251
 
248
252
  yield(server_app) if block_given?
249
253
  rescue => e
250
- raise CriticalDeployError.new(e, "Could not restart #{@name}")
254
+ raise DaemonError.new(e, "Could not restart #{@name}")
251
255
  end
252
256
  end
253
257
  end
254
258
  end
255
259
 
256
260
 
261
+ ##
262
+ # Wrap a command with a fail-specific exitcode and message.
263
+
264
+ def exit_on_failure cmd, exitcode=1, message=nil
265
+ "(#{cmd}) || (echo '#{message}' && exit #{exitcode});"
266
+ end
267
+
268
+
257
269
  ##
258
270
  # Gets the command that starts the daemon.
259
271
  # Should be overridden by child classes.
260
272
 
261
273
  def start_cmd
262
- return @start_cmd ||
263
- raise(CriticalDeployError, "@start_cmd undefined. Can't start #{@name}")
274
+ return @start_cmd if @start_cmd
275
+ raise DaemonError, "start_cmd undefined for #{@name}"
276
+ end
277
+
278
+
279
+ ##
280
+ # Start command wrapped with an exit_on_failure handler.
281
+
282
+ def _start_cmd
283
+ exit_on_failure start_cmd, START_FAILED_CODE,
284
+ "Could not start #{@name} for #{@app.name}"
264
285
  end
265
286
 
266
287
 
@@ -269,26 +290,59 @@ module Sunshine
269
290
 
270
291
  def stop_cmd
271
292
  "test -f #{@pid} && kill -#{@sigkill} $(cat #{@pid}) && sleep 1 && "+
272
- "rm -f #{@pid} || echo 'Could not kill #{@name} pid for #{@app.name}';"
293
+ "rm -f #{@pid}"
294
+ end
295
+
296
+
297
+ ##
298
+ # Stop command wrapped with an exit_on_failure handler.
299
+
300
+ def _stop_cmd
301
+ exit_on_failure stop_cmd, STOP_FAILED_CODE,
302
+ "Could not kill #{@name} pid for #{@app.name}"
273
303
  end
274
304
 
275
305
 
276
306
  ##
277
307
  # Gets the command that restarts the daemon.
308
+ # Should be overridden by child classes if different
309
+ # from start_cmd && stop_cmd.
278
310
 
279
311
  def restart_cmd
280
- @restart_cmd || [stop_cmd, start_cmd].map{|c| "(#{c})"}.join(" && ")
312
+ @restart_cmd
281
313
  end
282
314
 
283
315
 
284
316
  ##
285
- # Get the command to check if the daemon is running.
317
+ # Restart command wrapped with an exit_on_failure handler.
318
+
319
+ def _restart_cmd
320
+ if restart_cmd
321
+ exit_on_failure restart_cmd, RESTART_FAILED_CODE,
322
+ "Could not restart #{@name} for #{@app.name}"
323
+ else
324
+ "(#{_stop_cmd}) && (#{_start_cmd});"
325
+ end
326
+ end
327
+
328
+
329
+ ##
330
+ # Status command wrapped with an exit_on_failure handler.
286
331
 
287
332
  def status_cmd
288
333
  @status_cmd || "test -f #{@pid} && kill -0 $(cat #{@pid})"
289
334
  end
290
335
 
291
336
 
337
+ ##
338
+ # Get the command to check if the daemon is running.
339
+
340
+ def _status_cmd
341
+ exit_on_failure status_cmd, STATUS_DOWN_CODE,
342
+ "#{@app.name} #{@name} is not running"
343
+ end
344
+
345
+
292
346
  ##
293
347
  # Append or override daemon log file paths:
294
348
  # daemon.log_files :stderr => "/all_logs/stderr.log"
@@ -390,7 +444,7 @@ module Sunshine
390
444
  ##
391
445
  # Make sure log files are owned by the daemon's user.
392
446
 
393
- def touch_log_files shell
447
+ def chown_log_files shell
394
448
  files = @log_files.values.join(" ")
395
449
 
396
450
  sudo = pick_sudo(shell)
@@ -401,8 +455,8 @@ module Sunshine
401
455
  nil
402
456
  end
403
457
 
404
- shell.call "touch #{files}", :sudo => true
405
- shell.call "chown #{user} #{files}", :sudo => true if user
458
+ return unless user
459
+ shell.call "chown -f #{user} #{files}", :sudo => true rescue nil
406
460
  end
407
461
 
408
462
 
@@ -419,7 +473,7 @@ module Sunshine
419
473
  %w{start stop restart status}.each do |script|
420
474
  script_file = "#{@config_path}/#{script}"
421
475
 
422
- cmd = send "#{script}_cmd".to_sym
476
+ cmd = send "_#{script}_cmd"
423
477
 
424
478
  sa.shell.make_file script_file, cmd,
425
479
  :flags => '--chmod=ugo=rwx'
@@ -36,8 +36,6 @@ module Sunshine
36
36
 
37
37
  class DependencyLib
38
38
 
39
- class MissingDependency < Exception; end
40
-
41
39
  ##
42
40
  # Array of all dependency classes. Appended to automatically when
43
41
  # DependencyLib::Dependency is inherited.
@@ -17,38 +17,56 @@ module Sunshine
17
17
 
18
18
  ##
19
19
  # An error occurred when attempting to run a command on the local system
20
- class CmdError < Exception; end
20
+ class CmdError < Exception;
21
+ attr_reader :exit_code
21
22
 
22
-
23
- ##
24
- # An ssh call returned a non-zero exit code
25
- class SSHCmdError < CmdError
26
- attr_reader :shell
27
- def initialize message=nil, shell=nil
28
- @shell = shell
29
- super(message)
23
+ def initialize exit_code, cmd=nil
24
+ message = "Execution failed with status #{exit_code}: #{cmd}"
25
+ super message
26
+ @exit_code = exit_code
30
27
  end
31
28
  end
32
29
 
33
30
 
31
+ ##
32
+ # A shell command timed out.
33
+ class TimeoutError < Exception; end
34
+
35
+
36
+ ##
37
+ # Remote connection to server failed.
38
+ class ConnectionError < Exception; end
39
+
40
+
34
41
  ##
35
42
  # Something went wrong with a deploy-specific item.
36
43
  class DeployError < Exception; end
37
44
 
38
45
 
39
46
  ##
40
- # The error is serious enough that deploy cannot proceed.
41
- # Sunshine will attempt to revert to a previous deploy if available.
42
- class CriticalDeployError < DeployError; end
47
+ # Something went wrong with a daemon-specific item.
48
+ class DaemonError < Exception; end
43
49
 
44
50
 
45
51
  ##
46
- # The error is so serious that all no more action can be taken.
47
- # Sunshine will attempt to close any ssh connections and stop the deploy.
48
- class FatalDeployError < DeployError; end
52
+ # Something went wrong with a dependency-specific item.
53
+ class DependencyError < Exception; end
54
+
55
+
56
+ ##
57
+ # Dependency requested could not be found.
58
+ class MissingDependency < DependencyError; end
59
+
49
60
 
50
61
  ##
51
- # A dependency could not be installed.
52
- class DependencyError < FatalDeployError; end
62
+ # Dependency failed to install.
63
+ class InstallError < DependencyError; end
53
64
 
65
+ ##
66
+ # Dependency failed to uninstall.
67
+ class UninstallError < DependencyError; end
68
+
69
+ ##
70
+ # Something went wrong with a scm-specific item.
71
+ class RepoError < Exception; end
54
72
  end
@@ -32,7 +32,7 @@ module Sunshine
32
32
  # Checks if dependency type is valid for a given shell.
33
33
 
34
34
  def self.system_manager? shell=nil
35
- (shell || Sunshine.shell).syscall "apt-get --version"
35
+ (shell || Sunshine.shell).system "apt-get --version"
36
36
  end
37
37
 
38
38
 
@@ -43,11 +43,6 @@ module Sunshine
43
43
 
44
44
  class Dependency
45
45
 
46
- class CmdError < Exception; end
47
- class InstallError < Exception; end
48
- class UninstallError < Exception; end
49
-
50
-
51
46
  ##
52
47
  # Check if sudo should be used
53
48
 
@@ -34,7 +34,7 @@ module Sunshine
34
34
  # Checks if dependency type is valid for a given shell.
35
35
 
36
36
  def self.system_manager? shell=nil
37
- (shell || Sunshine.shell).syscall "yum --version"
37
+ (shell || Sunshine.shell).system "yum --version"
38
38
  end
39
39
 
40
40
 
@@ -11,8 +11,6 @@ module Sunshine
11
11
 
12
12
  class RemoteShell < Shell
13
13
 
14
- class ConnectionError < FatalDeployError; end
15
-
16
14
  ##
17
15
  # The loop to keep the ssh connection open.
18
16
  LOGIN_LOOP = "echo ok; echo ready; "+
@@ -61,7 +59,7 @@ module Sunshine
61
59
 
62
60
  @user ||= options[:user]
63
61
 
64
- @rsync_flags = ["-azP"]
62
+ @rsync_flags = ["-azrP"]
65
63
  @rsync_flags.concat [*options[:rsync_flags]] if options[:rsync_flags]
66
64
 
67
65
  @ssh_flags = [
@@ -170,7 +168,25 @@ module Sunshine
170
168
  # Checks if the given file exists
171
169
 
172
170
  def file? filepath
173
- syscall "test -f #{filepath}"
171
+ self.system "test -f #{filepath}"
172
+ end
173
+
174
+
175
+ ##
176
+ # Start an interactive shell with preset permissions and env.
177
+ # Optionally pass a command to be run first.
178
+
179
+ def tty! cmd=nil
180
+ sync do
181
+ cmd = [cmd, "sh -il"].compact.join " && "
182
+ cmd = quote_cmd cmd
183
+
184
+ pid = fork do
185
+ exec \
186
+ ssh_cmd(sudo_cmd(env_cmd(cmd)), :flags => "-t").to_a.join(" ")
187
+ end
188
+ Process.waitpid pid
189
+ end
174
190
  end
175
191
 
176
192
 
@@ -202,7 +218,7 @@ module Sunshine
202
218
 
203
219
 
204
220
  ##
205
- # Uploads a file via rsync
221
+ # Uploads a file via rsync.
206
222
 
207
223
  def upload from_path, to_path, options={}, &block
208
224
  to_path = "#{@host}:#{to_path}"
@@ -1,7 +1,5 @@
1
1
  module Sunshine
2
2
 
3
- class RepoError < Exception; end
4
-
5
3
  ##
6
4
  # An abstract class to wrap simple basic scm features. The primary function
7
5
  # of repo objects is to get information about the scm branch that is being
@@ -80,12 +78,12 @@ module Sunshine
80
78
  # Defaults to false. Subclasses must override this method to enable
81
79
  # auto detecting of a given scm implementation.
82
80
 
83
- def self.valid?
81
+ def self.valid? *args
84
82
  false
85
83
  end
86
84
 
87
85
 
88
- attr_reader :url, :scm
86
+ attr_accessor :url, :scm, :flags
89
87
 
90
88
  def initialize url, options={}
91
89
  @scm = self.class.name.split("::").last.sub('Repo', '').downcase
@@ -106,11 +104,12 @@ module Sunshine
106
104
  shell.call "test -d #{path} && rm -rf #{path} || echo false"
107
105
  shell.call "mkdir -p #{path}"
108
106
 
109
- do_checkout path, shell
110
- get_repo_info path, shell
107
+ do_checkout path, shell
108
+ get_repo_info path, shell
111
109
  end
112
110
 
113
111
 
112
+
114
113
  ##
115
114
  # Checkout the repo - implemented by subclass
116
115
 
@@ -12,7 +12,7 @@ module Sunshine
12
12
 
13
13
  def initialize url, options={}
14
14
  super
15
- @flags << '-r' << '--exclude .svn/' << '--exclude .git/'
15
+ @flags << '--exclude .svn/' << '--exclude .git/'
16
16
  @url << "/" unless @url[-1..-1] == "/"
17
17
  end
18
18
 
@@ -33,7 +33,7 @@ module Sunshine
33
33
 
34
34
 
35
35
  ##
36
- # Define an attribue that will get a value from app, or locally if
36
+ # Define an attribute that will get a value from app, or locally if
37
37
  # @app isn't set.
38
38
 
39
39
  def self.app_attr *attribs
@@ -74,6 +74,23 @@ module Sunshine
74
74
  end
75
75
 
76
76
 
77
+ ##
78
+ # Creates a ServerApp instance from a deploy info file.
79
+
80
+ def self.from_info_file path, shell=nil
81
+ shell ||= Sunshine.shell
82
+
83
+ opts = YAML.load shell.call("cat #{path}")
84
+ opts[:root_path] = opts.delete :path
85
+
86
+ sa_shell = shell.dup
87
+ sa_shell.env = opts[:env] || Hash.new
88
+ sa_shell.connect if shell.connected?
89
+
90
+ new opts[:name], sa_shell, opts
91
+ end
92
+
93
+
77
94
  app_attr :name, :deploy_name
78
95
  app_attr :root_path, :checkout_path, :current_path
79
96
  app_attr :deploys_path, :log_path, :shared_path, :scripts_path
@@ -81,6 +98,13 @@ module Sunshine
81
98
  attr_accessor :app, :roles, :scripts, :info, :shell, :crontab, :health
82
99
  attr_writer :pkg_manager
83
100
 
101
+ ##
102
+ # Create a server app instance. Supports the following
103
+ # argument configurations:
104
+ #
105
+ # ServerApp.new app_inst, "myserver.com", :roles => :web
106
+ # ServerApp.new "app_name", shell_inst, options_hash
107
+
84
108
  def initialize app, host, options={}
85
109
 
86
110
  @app = App === app ? app : nil
@@ -108,6 +132,9 @@ module Sunshine
108
132
 
109
133
  @crontab = Crontab.new name, @shell
110
134
  @health = Healthcheck.new shared_path, @shell
135
+
136
+ @all_deploy_names = nil
137
+ @previous_deploy_name = nil
111
138
  end
112
139
 
113
140
 
@@ -140,7 +167,7 @@ module Sunshine
140
167
  end
141
168
 
142
169
  if build_scripts[:status].empty?
143
- build_scripts[:status] << "echo 'No daemons for #{self.name}'; exit 1;"
170
+ build_scripts[:status] << "echo 'No status for #{self.name}'; exit 1;"
144
171
  end
145
172
 
146
173
  build_scripts.each do |name, cmds|
@@ -152,6 +179,8 @@ module Sunshine
152
179
 
153
180
  write_script name, bash
154
181
  end
182
+
183
+ symlink_scripts_to_root
155
184
  end
156
185
 
157
186
 
@@ -194,7 +223,7 @@ module Sunshine
194
223
  def deploy_details reload=false
195
224
  return @deploy_details if @deploy_details && !reload
196
225
  @deploy_details =
197
- YAML.load @shell.call("cat #{self.scripts_path}/info") rescue nil
226
+ YAML.load @shell.call("cat #{self.root_path}/info") rescue nil
198
227
 
199
228
  @deploy_details = nil unless Hash === @deploy_details
200
229
 
@@ -234,8 +263,11 @@ module Sunshine
234
263
  :deployed_as => @shell.call("whoami"),
235
264
  :deployed_by => Sunshine.shell.user,
236
265
  :deploy_name => File.basename(self.checkout_path),
266
+ :name => self.name,
267
+ :env => shell_env,
237
268
  :roles => @roles,
238
- :path => self.root_path
269
+ :path => self.root_path,
270
+ :sunshine_version => Sunshine::VERSION
239
271
  }.merge @info
240
272
  end
241
273
 
@@ -348,6 +380,31 @@ fi
348
380
  end
349
381
 
350
382
 
383
+ ##
384
+ # Returns an array of all deploys in the deploys_path dir,
385
+ # starting with the oldest.
386
+
387
+ def all_deploy_names reload=false
388
+ return @all_deploy_names if @all_deploy_names && !reload
389
+
390
+ @all_deploy_names =
391
+ @shell.call("ls -rc1 #{self.deploys_path}").split("\n")
392
+ end
393
+
394
+
395
+ ##
396
+ # Returns the name of the previous deploy.
397
+
398
+ def previous_deploy_name reload=false
399
+ return @previous_deploy_name if @previous_deploy_name && !reload
400
+
401
+ arr = all_deploy_names(reload)
402
+ arr.delete(@deploy_name)
403
+
404
+ @previous_deploy_name = arr.last
405
+ end
406
+
407
+
351
408
  ##
352
409
  # Run a rake task the deploy server.
353
410
 
@@ -370,7 +427,7 @@ fi
370
427
  # based on Sunshine's max_deploy_versions.
371
428
 
372
429
  def remove_old_deploys
373
- deploys = @shell.call("ls -1 #{self.deploys_path}").split("\n")
430
+ deploys = all_deploy_names true
374
431
 
375
432
  return unless deploys.length > Sunshine.max_deploy_versions
376
433
 
@@ -388,7 +445,18 @@ fi
388
445
  # Post-deploy only.
389
446
 
390
447
  def restart
391
- @shell.call "#{self.root_path}/restart" rescue false
448
+ # Permissions are handled by the script, use: :sudo => false
449
+ run_script :stop, :sudo => false
450
+ end
451
+
452
+
453
+ ##
454
+ # Run the app's restart script. Raises an exception on failure.
455
+ # Post-deploy only.
456
+
457
+ def restart!
458
+ # Permissions are handled by the script, use: :sudo => false
459
+ run_script! :restart, :sudo => false
392
460
  end
393
461
 
394
462
 
@@ -399,7 +467,7 @@ fi
399
467
  def revert!
400
468
  @shell.call "rm -rf #{self.checkout_path}"
401
469
 
402
- last_deploy = @shell.call("ls -rc1 #{self.deploys_path}").split("\n").last
470
+ last_deploy = previous_deploy_name(true)
403
471
 
404
472
  if last_deploy && !last_deploy.empty?
405
473
  @shell.symlink "#{self.deploys_path}/#{last_deploy}", self.current_path
@@ -438,13 +506,25 @@ fi
438
506
 
439
507
 
440
508
  ##
441
- # Runs a script from the script_path.
509
+ # Runs a script from the root_path.
442
510
  # Post-deploy only.
443
511
 
444
512
  def run_script name, options=nil, &block
445
513
  options ||= {}
446
- @shell.call \
447
- File.join(self.scripts_path, name.to_s), options, &block rescue false
514
+ run_script! name, options, &block rescue false
515
+ end
516
+
517
+
518
+ ##
519
+ # Runs a script from the root_path. Raises an exception if the status
520
+ # code is not 0.
521
+ # Post-deploy only.
522
+
523
+ def run_script! name, options=nil, &block
524
+ options ||= {}
525
+
526
+ script_path = File.join self.root_path, name.to_s
527
+ @shell.call script_path, options, &block
448
528
  end
449
529
 
450
530
 
@@ -454,7 +534,12 @@ fi
454
534
 
455
535
  def running?
456
536
  # Permissions are handled by the script, use: :sudo => false
457
- run_script :status, :sudo => false
537
+ run_script! :status, :sudo => false
538
+ true
539
+
540
+ rescue CmdError => e
541
+ return false if e.exit_code == Daemon::STATUS_DOWN_CODE
542
+ raise e
458
543
  end
459
544
 
460
545
 
@@ -499,6 +584,23 @@ fi
499
584
  end
500
585
 
501
586
 
587
+ ##
588
+ # Run the app's start script. Raises an exception on failure.
589
+ # Post-deploy only.
590
+
591
+ def start! options=nil
592
+ options ||= {}
593
+
594
+ if running?
595
+ return unless options[:force]
596
+ stop!
597
+ end
598
+
599
+ # Permissions are handled by the script, use: :sudo => false
600
+ run_script! :start, :sudo => false
601
+ end
602
+
603
+
502
604
  ##
503
605
  # Get the app's status: :running or :down.
504
606
 
@@ -517,6 +619,16 @@ fi
517
619
  end
518
620
 
519
621
 
622
+ ##
623
+ # Run the app's stop script. Raises an exception on failure.
624
+ # Post-deploy only.
625
+
626
+ def stop!
627
+ # Permissions are handled by the script, use: :sudo => false
628
+ run_script! :stop, :sudo => false
629
+ end
630
+
631
+
520
632
  ##
521
633
  # Creates a symlink to the app's checkout path.
522
634
 
@@ -525,12 +637,34 @@ fi
525
637
  end
526
638
 
527
639
 
640
+ ##
641
+ # Creates a symlink of every script in the scripts_path dir in the
642
+ # app's root directory for easy access.
643
+
644
+ def symlink_scripts_to_root
645
+ scripts = @shell.call("ls -1 #{self.scripts_path}").split("\n")
646
+
647
+ scripts.each do |name|
648
+ script_file = File.join self.scripts_path, name
649
+ pointer_file = File.join self.root_path, name
650
+
651
+ @shell.symlink script_file, pointer_file
652
+ end
653
+ end
654
+
655
+
528
656
  ##
529
657
  # Assumes the passed code_dir is the root directory of the checked out
530
658
  # codebase and uploads it to the checkout_path.
531
659
 
532
660
  def upload_codebase code_dir, scm_info={}
533
- RsyncRepo.new(code_dir).checkout_to self.checkout_path, @shell
661
+ excludes = scm_info.delete :exclude if scm_info[:exclude]
662
+ excludes = [excludes].flatten.compact
663
+ excludes.map!{|e| "--exclude #{e}"}
664
+
665
+ repo = RsyncRepo.new code_dir, :flags => excludes
666
+ repo.checkout_to self.checkout_path, @shell
667
+
534
668
  @info[:scm] = scm_info
535
669
  end
536
670
 
@@ -583,8 +717,6 @@ fi
583
717
 
584
718
  @shell.make_file script_file, contents,
585
719
  :flags => '--chmod=ugo=rwx' unless @shell.file? script_file
586
-
587
- @shell.symlink script_file, "#{self.root_path}/#{name}"
588
720
  end
589
721
 
590
722