simple_backup 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +2 -1
  4. data/backup_example.rb +23 -13
  5. data/lib/simple_backup/backend/abstract.rb +35 -0
  6. data/lib/simple_backup/backend/local.rb +45 -0
  7. data/lib/simple_backup/backends.rb +60 -0
  8. data/lib/simple_backup/dsl.rb +26 -93
  9. data/lib/simple_backup/engine.rb +41 -3
  10. data/lib/simple_backup/source/abstract.rb +130 -0
  11. data/lib/simple_backup/source/dir.rb +39 -0
  12. data/lib/simple_backup/source/dir_strategy/bare.rb +17 -0
  13. data/lib/simple_backup/source/dir_strategy/capistrano.rb +41 -0
  14. data/lib/simple_backup/source/file.rb +20 -0
  15. data/lib/simple_backup/source/mysql.rb +28 -0
  16. data/lib/simple_backup/sources.rb +74 -0
  17. data/lib/simple_backup/utils/disk_usage.rb +53 -0
  18. data/lib/simple_backup/utils/logger.rb +92 -0
  19. data/lib/simple_backup/utils/mailer.rb +126 -0
  20. data/lib/simple_backup/utils/mysql.rb +65 -0
  21. data/lib/simple_backup/utils.rb +4 -53
  22. data/lib/simple_backup/version.rb +2 -2
  23. data/lib/simple_backup.rb +40 -5
  24. metadata +17 -19
  25. data/lib/simple_backup/engine/abstract.rb +0 -42
  26. data/lib/simple_backup/engine/app_strategy/abstract.rb +0 -13
  27. data/lib/simple_backup/engine/app_strategy/bare.rb +0 -29
  28. data/lib/simple_backup/engine/app_strategy/capistrano.rb +0 -44
  29. data/lib/simple_backup/engine/app_strategy/factory.rb +0 -20
  30. data/lib/simple_backup/engine/apps.rb +0 -63
  31. data/lib/simple_backup/engine/mysql.rb +0 -104
  32. data/lib/simple_backup/exception/app_already_defined.rb +0 -6
  33. data/lib/simple_backup/exception/apps_dir_does_not_exists.rb +0 -6
  34. data/lib/simple_backup/exception/base.rb +0 -6
  35. data/lib/simple_backup/exception/cant_create_dir.rb +0 -6
  36. data/lib/simple_backup/exception/type_does_not_exists.rb +0 -6
  37. data/lib/simple_backup/exception.rb +0 -5
  38. data/lib/simple_backup/logger.rb +0 -84
  39. data/lib/simple_backup/mailer.rb +0 -138
  40. data/lib/simple_backup/storage.rb +0 -96
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cd3e8ec26688c63d6e75643e4b962556b330c9ef
4
- data.tar.gz: d721a3e4ee4cea8064440289e527f9633c553a17
3
+ metadata.gz: db18d1aeca0f0b1e397e8599de6ed70474fd49e1
4
+ data.tar.gz: 9e70279a4648074f77da59a0f070a869ce8c555d
5
5
  SHA512:
6
- metadata.gz: ba862bc3835154a09912e16c85eab02c7b8defc932dfb755f5d5d6fcaf4f473e1ee87109194520f0a57bbac34188458000ad37aeab9b4d7549845840f8cda6cc
7
- data.tar.gz: f4eb040ff8315e5b35690ceb309237e8341090185376837e677afa50c92e7b457bbeda0b5f7b87a84142946974c4384f745b32241e5fff18aa1593b7d77aa13e
6
+ metadata.gz: d94e122a1bfab65a376e29087564568917c939149cec9d52c37d5905dd683687b033b1132fb28bcf6cf373da9f0fc3cb6492fd8cc30fe1c09bb2d38ef3412421
7
+ data.tar.gz: 00bb1b44ad48000765a9aa84817cc451772886beb03dc41e07a7503cbcf3d2e4d1293844de049caa6987d5d11a0221f57534453cafa9904ff4356d38eae1c54b
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  /pkg/
2
+ /Dockerfile
2
3
  /Gemfile.lock
data/README.md CHANGED
@@ -9,7 +9,8 @@ development and its API should be treat as unstable.
9
9
  ## TODO
10
10
 
11
11
  - [ ] Refactorization
12
- - [ ] Few backend for backup store (file, s3, ftp)
12
+ - [ ] Few backends for backup store (file, s3, ftp)
13
+ - [ ] Filters mechanism (e.g. PGP encryption)
13
14
  - [ ] Tests
14
15
  - [ ] Docummentation and examples
15
16
 
data/backup_example.rb CHANGED
@@ -1,31 +1,41 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
3
6
  require 'simple_backup'
4
7
 
5
8
  SimpleBackup.run do
6
- # log_level :debug
7
- backup_dir '/backup'
9
+ log_level :debug
10
+
8
11
  high_usage_treshold 0.9
9
12
  check_disk_path '/'
10
13
  check_disk_path '/backup'
11
14
  check_disk_path '/home/app'
12
15
 
13
- apps do
14
- keep_last 9
16
+ default_keep_last 9
17
+
18
+ sources do
19
+ dir 'app-1', '/home/app/app-1', type: :capistrano, backends: 'backup'
20
+ dir 'app-2', '/home/app/app-2', backends: :none
21
+ dir 'none', '/none'
22
+
23
+ file 'hosts', '/etc/hosts'
15
24
 
16
- app '/home/app/app-1', type: :capistrano
17
- app '/home/app/app-2', type: :bare
25
+ mysql 'test1', 'test1'
26
+ mysql 'test2', 'test2'
27
+ mysql 'test3', 'test3', exclude_tables: ['t_test1']
18
28
  end
19
29
 
20
- mysql do
21
- keep_last 9
30
+ backends do
31
+ local 'backup', path: '/srv/backup'
32
+ end
22
33
 
34
+ mysql do
23
35
  host 'localhost'
24
36
  port 3306
25
- user 'backup'
26
- pass 'backup'
27
- db 'test1'
28
- db 'test2', exclude_tables: ['t_test1']
37
+ user 'root'
38
+ pass 'root'
29
39
  end
30
40
 
31
41
  mailer do
@@ -33,7 +43,7 @@ SimpleBackup.run do
33
43
 
34
44
  from 'backup@localhost'
35
45
  to 'root@localhost'
36
- cc 'root@localhost'
46
+ cc 'rb@localhost'
37
47
  bcc 'root@localhost'
38
48
  end
39
49
  end
@@ -0,0 +1,35 @@
1
+ module SimpleBackup
2
+ module Backend
3
+ class Abstract
4
+ @@logger = Utils::Logger.instance
5
+
6
+ def configure(*args)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def name=(value)
11
+ @name = value.gsub(/[^a-zA-Z0-9\-\_\. ]*/, '').gsub(/\s+/, '_').downcase
12
+ end
13
+
14
+ def name
15
+ @name
16
+ end
17
+
18
+ def type
19
+ self.class.name.split('::').last.gsub(/[^a-zA-Z0-9\-\_\. ]*/, '').gsub(/\s+/, '_').downcase
20
+ end
21
+
22
+ def desc
23
+ '%5s :: %s' % [type, @name]
24
+ end
25
+
26
+ def store(source)
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def cleanup(source)
31
+ raise NotImplementedError
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ module SimpleBackup
2
+ module Backend
3
+ class Local < Abstract
4
+ def configure(options = {})
5
+ raise "Must provide :path option" unless options[:path]
6
+
7
+ @path = options[:path]
8
+
9
+ raise "#{@path} does not exists" unless ::File.exist?(@path)
10
+ raise "#{@path} is not a directory" unless ::File.directory?(@path)
11
+ raise "#{@path} is not writable" unless ::File.writable?(@path)
12
+ end
13
+
14
+ def store(source)
15
+ storage_path = get_storage_path(source)
16
+ FileUtils.cp source.backup_file, storage_path
17
+ end
18
+
19
+ def cleanup(source)
20
+ storage_path = get_storage_path(source)
21
+
22
+ files = ::Dir.glob(::File.join(storage_path, '*.tar.gz')).sort
23
+
24
+ to_persist = files
25
+ to_persist = files.slice(source.keep_last * -1, source.keep_last) if files.length > source.keep_last
26
+ to_remove = files - to_persist
27
+
28
+ @@logger.scope_start
29
+ to_remove.each do |file|
30
+ FileUtils.rm(file)
31
+ @@logger.debug "Old backup '#{file}' for source '#{source.desc.strip}' cleaned up from '#{desc.strip}'"
32
+ end
33
+ @@logger.scope_end
34
+ end
35
+
36
+ private
37
+ def get_storage_path(source)
38
+ path = ::File.join(@path, source.type, source.name)
39
+ FileUtils.mkpath path unless ::File.exist?(path)
40
+
41
+ path
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,60 @@
1
+ require 'singleton'
2
+ require 'simple_backup/backend/abstract'
3
+
4
+ module SimpleBackup
5
+ class Backends
6
+ include Singleton
7
+
8
+ @@logger = Utils::Logger.instance
9
+ @@sources = Sources.instance
10
+
11
+ def initialize
12
+ @backends = {}
13
+ end
14
+
15
+ def each(&block)
16
+ @backends.each(&block)
17
+ end
18
+
19
+ def save_and_cleanup
20
+ each do |name, backend|
21
+ @@sources.each do |name, source|
22
+ next unless source.backup_file and source.supports(backend)
23
+
24
+ backend.store(source)
25
+ @@logger.info "Source '#{source.desc.strip}' stored in backend '#{backend.desc.strip}'"
26
+
27
+ backend.cleanup(source)
28
+ @@logger.info "Source '#{source.desc.strip}' cleaned up in backend '#{backend.desc.strip}'"
29
+ end
30
+ end
31
+ end
32
+
33
+ def method_missing(method, *args)
34
+ backend = create_backend(method)
35
+
36
+ return nil if backend.nil?
37
+
38
+ name = args.shift
39
+ options = args.shift
40
+ options ||= {}
41
+
42
+ raise "Name '#{name}' for backend already used" if @backends.has_key?(name.to_sym)
43
+
44
+ backend.name = name
45
+ backend.configure(options)
46
+
47
+ @@logger.info "Created backend for: #{backend.desc}"
48
+ @backends[name.to_sym] = backend
49
+ end
50
+
51
+ private
52
+ def create_backend(name)
53
+ file = "simple_backup/backend/#{name}"
54
+
55
+ require file
56
+ backend_name = Object.const_get("SimpleBackup::Backend::#{name.capitalize}")
57
+ backend_name.new
58
+ end
59
+ end
60
+ end
@@ -1,120 +1,53 @@
1
1
  module SimpleBackup
2
- TIMESTAMP = Time.new.strftime('%Y%m%d%H%M%S')
3
- @@status = :failed
4
-
5
- def self.status
6
- @@status
7
- end
8
-
9
- def self.run(&block)
10
- Logger::info "Backup #{TIMESTAMP} started"
11
-
12
- dsl = DSL.new
13
-
14
- Logger::scope_start :info, "Configuration"
15
- dsl.instance_eval(&block)
16
- dsl.prepare
17
- Logger::scope_end
18
-
19
- dsl.run
20
- dsl.cleanup
21
- @@status = :succeed
22
-
23
- Logger::info "Backup #{TIMESTAMP} finished"
24
- rescue StandardError => e
25
- self.handle_exception(e)
26
- ensure
27
- dsl.notify
28
- Logger::info "Notifications for backup #{TIMESTAMP} finished"
29
- end
30
-
31
- def self.handle_exception(e)
32
- Logger::error "#{e.class} => #{e.message}"
33
- Logger::error "Backup #{TIMESTAMP} failed"
34
- STDERR.puts "Error @ #{Time.new.strftime('%Y-%m-%dT%H:%M:%S')}"
35
- STDERR.puts "#{e.inspect}"
36
- STDERR.puts e.backtrace
37
- end
38
-
39
2
  class DSL
40
- def initialize
41
- @storage = Storage.new
42
- end
43
-
44
- def prepare
45
- if @apps_block
46
- @apps = Engine::Apps.new
47
- @apps.storage = @storage
48
- @apps.instance_eval(&@apps_block)
49
- end
50
-
51
- if @mysql_block
52
- @mysql = Engine::MySQL.new
53
- @mysql.storage = @storage
54
- @mysql.instance_eval(&@mysql_block)
55
- end
56
- end
57
-
58
- def run
59
- usage = Utils::Disk::usage
60
- Logger::error "Disk high usage treshold exceeded #{usage[:high_usage]}" if usage[:high_usage_exceeded]
3
+ @@logger = Utils::Logger.instance
61
4
 
62
- Logger::scope_start :info, "Backup job"
63
- @apps.backup if @apps
64
- @mysql.backup if @mysql
65
- Logger::scope_end
5
+ def initialize(engine)
6
+ @engine = engine
66
7
  end
67
8
 
68
- def cleanup
69
- Logger::scope_start :info, "Cleanup job"
70
- @apps.cleanup if @apps
71
- @mysql.cleanup if @mysql
72
- Logger::scope_end
73
- end
74
-
75
- def notify
76
- @mailer.send if @mailer
77
- rescue StandardError => e
78
- SimpleBackup.handle_exception(e)
9
+ def log_level(level)
10
+ @@logger.level = level
79
11
  end
80
12
 
81
- def sources
82
- sources = {}
83
- sources[:apps] = @apps.sources if @apps
84
- sources[:mysql] = @mysql.sources if @mysql
13
+ def high_usage_treshold(value)
14
+ @@logger.info "Setting high_usage_treshold to #{value}"
85
15
 
86
- sources
16
+ Utils::Disk.high_usage_treshold = value
87
17
  end
88
18
 
89
- def log_level(level)
90
- Logger::level = level
91
- end
19
+ def check_disk_path(path)
20
+ @@logger.info "Adding disk path '#{path}' to usage check"
92
21
 
93
- def backup_dir(dir)
94
- @storage.dir = dir
22
+ Utils::Disk.add_path(path)
95
23
  end
96
24
 
97
- def high_usage_treshold(value)
98
- Logger::info "Setting high_usage_treshold to #{value}"
99
- Utils::Disk.high_usage_treshold = value
25
+ def default_keep_last(value)
26
+ Sources.instance.default_keep_last = value
100
27
  end
101
28
 
102
- def check_disk_path(path)
103
- Logger::info "Adding disk path '#{path}' to usage check"
104
- Utils::Disk.add_path(path)
29
+ def sources(&block)
30
+ sources = Sources.instance
31
+ sources.instance_eval(&block)
105
32
  end
106
33
 
107
- def apps(&block)
108
- @apps_block = block
34
+ def backends(&block)
35
+ backends = Backends.instance
36
+ backends.instance_eval(&block)
109
37
  end
110
38
 
111
39
  def mysql(&block)
112
- @mysql_block = block
40
+ @@logger.info "Configuring MySQL Util"
41
+
42
+ Utils::MySQL.instance.instance_eval(&block)
113
43
  end
114
44
 
115
45
  def mailer(&block)
116
- @mailer = Mailer.new(self, @storage)
46
+ @@logger.info "Configuring Mailer Util"
47
+
48
+ @mailer = Utils::Mailer.new
117
49
  @mailer.instance_eval(&block)
50
+ @engine.mailer = @mailer
118
51
  end
119
52
  end
120
53
  end
@@ -1,3 +1,41 @@
1
- require 'simple_backup/engine/abstract'
2
- require 'simple_backup/engine/apps'
3
- require 'simple_backup/engine/mysql'
1
+ module SimpleBackup
2
+ module Engine
3
+ class Engine
4
+
5
+ @@backends = Backends.instance
6
+ @@sources = Sources.instance
7
+ @@logger = Utils::Logger.instance
8
+ @@mysql = Utils::MySQL.instance
9
+
10
+ def mailer=(mailer)
11
+ @mailer = mailer
12
+ end
13
+
14
+ def run
15
+ usage = Utils::Disk::usage
16
+
17
+ @@logger.error "Disk high usage treshold exceeded #{usage[:high_usage]}" if usage[:high_usage_exceeded]
18
+ @@logger.scope_start :info, "Backup"
19
+
20
+ @@sources.backup
21
+ @@backends.save_and_cleanup
22
+ @@sources.cleanup
23
+
24
+ @@logger.scope_end
25
+ ensure
26
+ @@mysql.close
27
+ end
28
+
29
+ def notify
30
+ return unless @mailer
31
+ @@logger.scope_start :info, "Sending e-mail notification"
32
+
33
+ @mailer.send
34
+
35
+ @@logger.scope_end :info, "Notifications for backup #{TIMESTAMP} finished"
36
+ rescue StandardError => e
37
+ SimpleBackup.handle_exception(e)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,130 @@
1
+ require 'rubygems/package'
2
+ require 'tmpdir'
3
+ require 'zlib'
4
+
5
+ module SimpleBackup
6
+ module Source
7
+ class Abstract
8
+ @@logger = Utils::Logger.instance
9
+
10
+ def configure(*args)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def keep_last=(value)
15
+ @keep_last = value
16
+ end
17
+
18
+ def keep_last
19
+ @keep_last
20
+ end
21
+
22
+ def name=(value)
23
+ @name = value.gsub(/[^a-zA-Z0-9\-\_\. ]*/, '').gsub(/\s+/, '_').downcase
24
+ end
25
+
26
+ def name
27
+ @name
28
+ end
29
+
30
+ def type
31
+ self.class.name.split('::').last.gsub(/[^a-zA-Z0-9\-\_\. ]*/, '').gsub(/\s+/, '_').downcase
32
+ end
33
+
34
+ def desc
35
+ '%5s :: %s' % [type, @name]
36
+ end
37
+
38
+ def get
39
+ return @backup_file if @backup_file
40
+
41
+ @@logger.scope_start :info, "Getting archive for: #{desc}"
42
+
43
+ @tmp_dir = ::Dir.mktmpdir('simple_backup-')
44
+ @@logger.debug "Created tmp directory #{@tmp_dir}"
45
+
46
+ data_exists = prepare_data
47
+
48
+ @@logger.warning "No data for: #{desc}" unless data_exists
49
+ archive_data if data_exists
50
+
51
+ FileUtils.rm_rf(@tmp_dir)
52
+ @@logger.debug "Removed tmp directory #{@tmp_dir}"
53
+
54
+ @backup_file
55
+ ensure
56
+ @@logger.scope_end
57
+ end
58
+
59
+ def cleanup
60
+ return nil unless @backup_file
61
+
62
+ FileUtils.rm (@backup_file)
63
+ @@logger.debug "Temporary backup file #{@backup_file} was removed"
64
+ end
65
+
66
+ def backup_file
67
+ @backup_file
68
+ end
69
+
70
+ def backends=(value)
71
+ @backends = []
72
+ @backends = @backends + value if value.kind_of?(Array)
73
+ @backends << value unless value.kind_of?(Array)
74
+ end
75
+
76
+ def supports(backend)
77
+ return TRUE unless @backends
78
+ return FALSE unless @backends.include?(backend.name)
79
+
80
+ TRUE
81
+ end
82
+
83
+ private
84
+ def prepare_data
85
+ raise NotImplementedError
86
+ end
87
+
88
+ def archive_data
89
+ filename = "#{type}-#{name}.#{SimpleBackup::TIMESTAMP}.tar.gz"
90
+ @backup_file = ::File.join(::Dir.tmpdir, filename)
91
+
92
+ ::File.open(backup_file, 'w') do |f|
93
+ f.write targz.string
94
+ end
95
+
96
+ @@logger.debug "Backup saved to temporary file #{backup_file}"
97
+ end
98
+
99
+ def targz
100
+ path = @tmp_dir
101
+
102
+ content = StringIO.new('');
103
+ Gem::Package::TarWriter.new(content) do |tar|
104
+ ::Dir[::File.join(path, '**/*')].each do |file|
105
+ mode = ::File.stat(file).mode
106
+ relative_file = file.sub(/^#{Regexp::escape path}\/?/, '')
107
+
108
+ if ::File.directory?(file)
109
+ tar.mkdir(relative_file, mode)
110
+ else
111
+ tar.add_file relative_file, mode do |tf|
112
+ ::File.open(file, 'rb') do |f|
113
+ tf.write f.read
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ content.rewind
120
+
121
+ gz = StringIO.new('')
122
+ zip = Zlib::GzipWriter.new(gz)
123
+ zip.write content.string
124
+ zip.close
125
+
126
+ gz
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,39 @@
1
+ module SimpleBackup
2
+ module Source
3
+ class Dir < Abstract
4
+ def initialize
5
+ @strategy = :bare
6
+ end
7
+
8
+ def configure(path, options = {})
9
+ @path = path
10
+
11
+ raise "#{path} is a file - use File source instead of Dir" unless !::File.exist?(path) or ::File.directory?(path)
12
+ @strategy = options[:strategy] if options[:strategy]
13
+ end
14
+
15
+ private
16
+ def prepare_data
17
+ return false unless ::File.exist?(@path)
18
+
19
+ path_entries = get_path_entries
20
+ FileUtils.cp_r path_entries, @tmp_dir if path_entries
21
+
22
+ true
23
+ end
24
+
25
+ def get_path_entries
26
+ file = "simple_backup/source/dir_strategy/#{@strategy.to_s}"
27
+
28
+ require file
29
+ strategy_name = Object.const_get("SimpleBackup::Source::DirStrategy::#{@strategy.to_s.capitalize}")
30
+ strategy = strategy_name.new
31
+
32
+ strategy.get_entries(@path)
33
+ rescue Errno::ENOENT
34
+ @@logger.warning "Path '#{@path}' does not exists"
35
+ nil
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ module SimpleBackup
2
+ module Source
3
+ module DirStrategy
4
+ class Bare
5
+ def get_entries(path)
6
+ ::Dir.entries(path).map do |p|
7
+ if p.match(/^\.\.?$/)
8
+ nil
9
+ else
10
+ ::File.join(path, p)
11
+ end
12
+ end.compact
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ module SimpleBackup
2
+ module Source
3
+ module DirStrategy
4
+ class Capistrano
5
+ @@logger = Utils::Logger.instance
6
+
7
+ def get_entries(path)
8
+ shared = shared_path(path)
9
+ current = current_path(path)
10
+ paths = [current, shared].compact
11
+
12
+ if paths.empty?
13
+ @@logger.warning "No capistrano paths for application"
14
+ return nil
15
+ end
16
+
17
+ paths
18
+ end
19
+
20
+ private
21
+ def current_path(path)
22
+ current = ::Dir.new(::File.join(path, 'current') + '/')
23
+ @@logger.debug "Capistrano current path: #{current.path}"
24
+ current.path
25
+ rescue Errno::ENOENT
26
+ @@logger.warning "No capistrano current path for application"
27
+ nil
28
+ end
29
+
30
+ def shared_path(path)
31
+ shared = ::Dir.new(::File.join(path, 'shared'))
32
+ @@logger.debug "Capistrano shared path: #{shared.path}"
33
+ shared.path
34
+ rescue Errno::ENOENT
35
+ @@logger.warning "No capistrano shared path for application"
36
+ nil
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ module SimpleBackup
2
+ module Source
3
+ class File < Abstract
4
+ def configure(path, options = {})
5
+ @path = path
6
+
7
+ raise "#{path} is a directory - use Dir source instead of File" unless !::File.exist?(path) or ::File.file?(path)
8
+ end
9
+
10
+ private
11
+ def prepare_data
12
+ return false unless ::File.exist?(@path)
13
+
14
+ FileUtils.cp @path, @tmp_dir
15
+
16
+ true
17
+ end
18
+ end
19
+ end
20
+ end