simple_backup 0.2.1 → 0.3.0

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 (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
@@ -0,0 +1,28 @@
1
+ module SimpleBackup
2
+ module Source
3
+ class Mysql < Abstract
4
+ @@mysql = Utils::MySQL.instance
5
+
6
+ def configure(db, options = {})
7
+ @db = db
8
+
9
+ @exclude_tables = options[:exclude_tables] if options[:exclude_tables]
10
+ end
11
+
12
+ private
13
+ def prepare_data
14
+ @@mysql.open
15
+
16
+ tables = @@mysql.scan_tables(@db)
17
+ return false if tables.nil?
18
+
19
+ tables = tables - @exclude_tables if @exclude_tables
20
+ dumpfile = ::File.join(@tmp_dir, @db) + '.sql'
21
+
22
+ @@mysql.dump(@db, tables, dumpfile)
23
+
24
+ true
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,74 @@
1
+ require 'singleton'
2
+ require 'simple_backup/source/abstract'
3
+
4
+ module SimpleBackup
5
+ class Sources
6
+ include Singleton
7
+
8
+ @@logger = Utils::Logger.instance
9
+
10
+ def initialize
11
+ @sources = {}
12
+ @default_keep_last = 5
13
+ end
14
+
15
+ def default_keep_last=(value)
16
+ @default_keep_last = value
17
+ end
18
+
19
+ def each(&block)
20
+ @sources.each do |type, sources|
21
+ sources.each(&block)
22
+ end
23
+ end
24
+
25
+ def backup
26
+ @sources.each do |type, sources|
27
+ sources.each do |name, source|
28
+ source.get
29
+ end
30
+ end
31
+ end
32
+
33
+ def cleanup
34
+ each do |name, source|
35
+ source.cleanup
36
+ end
37
+ end
38
+
39
+ def method_missing(method, *args)
40
+ source = create_source(method)
41
+
42
+ return nil if source.nil?
43
+
44
+ name = args.shift
45
+ identifier = args.shift
46
+ options = args.shift
47
+ options ||= {}
48
+
49
+ type = source.type.to_sym
50
+ @sources[type] = {} if @sources[type].nil?
51
+ raise "Name '#{name}' for source #{type} already used" if @sources[type].has_key?(name.to_sym)
52
+
53
+ source.keep_last = @default_keep_last
54
+ source.keep_last = options[:keep_last] if options[:keep_last]
55
+ source.backends = options[:backends] if options[:backends]
56
+ source.name = name
57
+
58
+ source.configure(identifier, options)
59
+
60
+ @@logger.info "Created source for: #{source.desc.strip}"
61
+
62
+ @sources[type][name.to_sym] = source
63
+ end
64
+
65
+ private
66
+ def create_source(name)
67
+ file = "simple_backup/source/#{name}"
68
+
69
+ require file
70
+ source_name = Object.const_get("SimpleBackup::Source::#{name.capitalize}")
71
+ source_name.new
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,53 @@
1
+ module SimpleBackup
2
+ module Utils
3
+ class Disk
4
+ @@high_usage_treshold = 0.9
5
+ @@paths = []
6
+
7
+ def self.high_usage_treshold=(value)
8
+ @@high_usage_treshold = value.to_f
9
+
10
+ raise ArgumentError.new "Backuper::Utils::Disk::high_usage_treshold must be a float greater than zero" if @@high_usage_treshold <= 0.0
11
+ end
12
+
13
+ def self.high_usage_treshold
14
+ @@high_usage_treshold
15
+ end
16
+
17
+ def self.add_path(path)
18
+ @@paths << path unless @@paths.include?(path)
19
+ end
20
+
21
+ def self.usage
22
+ df = `df -m #{@@paths.join(' ')} 2>/dev/null`.split("\n")
23
+ df.shift
24
+
25
+ max_percent = 0.0;
26
+ df.map! do |row|
27
+ row = row.split(' ')
28
+
29
+ percent_usage = (row[4].gsub('%', '').to_f / 100).round(2)
30
+ row = {
31
+ mount: row[5],
32
+ fs: row[0],
33
+ size: row[1],
34
+ used: row[2],
35
+ available: row[3],
36
+ percent: percent_usage,
37
+ high_usage_exceeded: percent_usage >= @@high_usage_treshold
38
+ }
39
+
40
+ max_percent = row[:percent] if row[:percent] > max_percent
41
+
42
+ row
43
+ end
44
+
45
+ {
46
+ mounts: df.uniq,
47
+ high_usage_exceeded: max_percent >= @@high_usage_treshold,
48
+ high_usage: max_percent
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,92 @@
1
+ require 'colorize'
2
+ require 'singleton'
3
+
4
+ module SimpleBackup
5
+ module Utils
6
+ class Logger
7
+ include Singleton
8
+
9
+ TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
10
+
11
+ def initialize
12
+ @buffer = []
13
+ @scope = 0
14
+ @level = :info
15
+ @levels = {
16
+ debug: {weight: 3, color: :light_cyan},
17
+ info: {weight: 2, color: :green},
18
+ warning: {weight: 1, color: :light_yellow},
19
+ error: {weight: 0, color: :red}
20
+ }
21
+
22
+ banner = "LOG STARTED #{Time.new.strftime('%Y-%m-%dT%H:%M:%S')}"
23
+ banner2 = "SimpleBackup v#{SimpleBackup::Version::get}"
24
+
25
+ banner_length = 0
26
+ banner_length = banner.length if banner.length > banner_length
27
+ banner_length = banner2.length if banner2.length > banner_length
28
+ banner_length = 80 if 80 > banner_length
29
+
30
+ border = '=' * ((banner_length - banner.length) / 2).ceil.to_i
31
+ @buffer << "#{border}==[ #{banner} ]==#{border}"
32
+ border = '=' * ((banner_length - banner2.length) / 2).ceil.to_i
33
+ @buffer << "#{border}==[ #{banner2} ]==#{border}"
34
+
35
+ puts @buffer[0].green
36
+ puts @buffer[1].green
37
+ end
38
+
39
+ def level=(level)
40
+ check_level(level)
41
+ @level = level
42
+ end
43
+
44
+ def scope_start(level = nil, message = nil)
45
+ log level, message unless level.nil? and message.nil?
46
+ @scope += 1
47
+ end
48
+
49
+ def scope_end(level = nil, message = nil)
50
+ log level, message unless level.nil? and message.nil?
51
+ @scope -= 1 unless @scope == 0
52
+ end
53
+
54
+ def debug(message)
55
+ log(:debug, message)
56
+ end
57
+
58
+ def info(message)
59
+ log(:info, message)
60
+ end
61
+
62
+ def warning(message)
63
+ log(:warning, message)
64
+ end
65
+
66
+ def error(message)
67
+ log(:error, message)
68
+ end
69
+
70
+ def log(level, message)
71
+ check_level(level)
72
+
73
+ color = @levels[level][:color]
74
+ should_write = @levels[level][:weight] <= @levels[@level][:weight]
75
+
76
+ scope_prefix = '..' * @scope
77
+ message = "%s %7s: %s%s" % [Time.new.strftime(TIME_FORMAT), level.to_s.upcase, scope_prefix, message]
78
+ @buffer << message
79
+
80
+ puts message.colorize(color: color) if should_write
81
+ end
82
+
83
+ def check_level(level)
84
+ raise "Unknown logging level #{level}" unless @levels.has_key?(level)
85
+ end
86
+
87
+ def buffer
88
+ @buffer
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,126 @@
1
+ require 'mail'
2
+ require 'socket'
3
+
4
+ module SimpleBackup
5
+ module Utils
6
+ class Mailer
7
+ @@logger = Logger.instance
8
+
9
+ def initialize()
10
+ @to = []
11
+ @cc = []
12
+ @bcc = []
13
+ @hostname = Socket.gethostbyname(Socket.gethostname).first
14
+ end
15
+
16
+ def subject_prefix(prefix)
17
+ @subject_prefix = prefix
18
+ end
19
+
20
+ def from(from)
21
+ @from = from
22
+ end
23
+
24
+ def to(to)
25
+ @to << to
26
+ end
27
+
28
+ def cc(cc)
29
+ @cc << cc
30
+ end
31
+
32
+ def bcc(bcc)
33
+ @bcc << bcc
34
+ end
35
+
36
+ def send
37
+ @@logger.info "Setting sender to: #{@from}"
38
+ from = @from
39
+ @@logger.scope_start :info, "Adding recipients:"
40
+ to = @to
41
+ to.each do |mail|
42
+ @@logger.info "to: #{mail}"
43
+ end
44
+ cc = @cc
45
+ cc.each do |mail|
46
+ @@logger.info "cc: #{mail}"
47
+ end
48
+ bcc = @bcc
49
+ bcc.each do |mail|
50
+ @@logger.info "bcc: #{mail}"
51
+ end
52
+ @@logger.scope_end
53
+
54
+ @subject_prefix += '[FAILED]' if SimpleBackup.status == :failed
55
+
56
+ subject = "%s Backup %s for %s" % [@subject_prefix, TIMESTAMP, @hostname]
57
+ @@logger.debug "Subject: #{subject}"
58
+
59
+ body = get_body
60
+
61
+ mail = Mail.new do
62
+ from from
63
+ to to
64
+ cc cc
65
+ bcc bcc
66
+ subject subject.strip
67
+ body body
68
+ end
69
+
70
+ mail.delivery_method :sendmail
71
+ @@logger.debug "Setting delivery method to sendmail"
72
+
73
+ mail.deliver
74
+ @@logger.info "Notification sent"
75
+ end
76
+
77
+ private
78
+ def get_body
79
+ sources = ''
80
+
81
+ SimpleBackup::Sources.instance.each do |name, source|
82
+ sources += " - %s\n" % source.desc
83
+ end
84
+
85
+ body = <<MAIL
86
+ Hi,
87
+
88
+ Backup #{TIMESTAMP} was created!
89
+
90
+ Backup contains:
91
+ #{sources}
92
+ Disk usage after backup:
93
+ #{disk_usage}
94
+ Backup log:
95
+ ------------
96
+ #{@@logger.buffer.join("\n")}
97
+ ------------
98
+
99
+ Have a nice day,
100
+ SimpleBackup
101
+
102
+ --
103
+ Mail was send automatically
104
+ Do not respond!
105
+ MAIL
106
+
107
+ body
108
+ end
109
+
110
+ def disk_usage
111
+ content = "%16s %25s %12s %12s %12s %12s\n" % ['Mount', 'Filesystem', 'Size', 'Used', 'Available', 'Percent used']
112
+
113
+ usage = Utils::Disk::usage
114
+ usage[:mounts].each do |m|
115
+ percent_usage = (m[:percent] * 100).to_s
116
+ percent_usage = '(!!) ' + percent_usage if m[:high_usage_exceeded]
117
+ content += "%16s %25s %8s MiB %8s MiB %8s MiB %11s%%\n" % [m[:mount], m[:fs], m[:size], m[:used], m[:available], percent_usage]
118
+ end
119
+
120
+ content += "\nHigh usage treshold exceeded!\nMax usage is #{usage[:high_usage]} where treshold is set to #{Utils::Disk::high_usage_treshold}\n" if usage[:high_usage_exceeded]
121
+
122
+ content
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,65 @@
1
+ require 'singleton'
2
+ require 'mysql2'
3
+
4
+ module SimpleBackup
5
+ module Utils
6
+ class MySQL
7
+ include Singleton
8
+
9
+ @@logger = Logger.instance
10
+
11
+ def initialize
12
+ @host = 'localhost'
13
+ @port = 3306
14
+ @user = nil
15
+ @pass = nil
16
+ end
17
+
18
+ def open
19
+ return nil unless @conn.nil?
20
+
21
+ @conn = Mysql2::Client.new(host: @host, port: @port, username: @user, password: @pass)
22
+ @existing_dbs = []
23
+ @conn.query("SHOW DATABASES").each do |row|
24
+ @existing_dbs << row['Database']
25
+ end
26
+ end
27
+
28
+ def close
29
+ @conn.close unless @conn.nil?
30
+ end
31
+
32
+ def scan_tables(db)
33
+ return nil unless @existing_dbs.include?(db)
34
+
35
+ tables = []
36
+ @conn.query("SHOW TABLES FROM `#{db}`").each do |row|
37
+ tables << row["Tables_in_#{db}"]
38
+ end
39
+ tables
40
+ end
41
+
42
+ def dump(db, tables, dumpfile)
43
+ cmd = "mysqldump --flush-logs --flush-privileges --order-by-primary --complete-insert -C -h #{@host} -u #{@user} -p#{@pass} #{db} #{tables.join(' ')} > #{dumpfile}"
44
+ @@logger.debug "Running command: #{cmd}"
45
+ `#{cmd}`
46
+ end
47
+
48
+ def host(value)
49
+ @host = value
50
+ end
51
+
52
+ def port(value)
53
+ @port = value
54
+ end
55
+
56
+ def user(value)
57
+ @user = value
58
+ end
59
+
60
+ def pass(value)
61
+ @pass = value
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,53 +1,4 @@
1
- module SimpleBackup
2
- module Utils
3
- class Disk
4
- @@high_usage_treshold = 0.9
5
- @@paths = []
6
-
7
- def self.high_usage_treshold=(value)
8
- @@high_usage_treshold = value.to_f
9
-
10
- raise ArgumentError.new "Backuper::Utils::Disk::high_usage_treshold must be a float greater than zero" if @@high_usage_treshold <= 0.0
11
- end
12
-
13
- def self.high_usage_treshold
14
- @@high_usage_treshold
15
- end
16
-
17
- def self.add_path(path)
18
- @@paths << path unless @@paths.include?(path)
19
- end
20
-
21
- def self.usage
22
- df = `df -m #{@@paths.join(' ')} 2>/dev/null`.split("\n")
23
- df.shift
24
-
25
- max_percent = 0.0;
26
- df.map! do |row|
27
- row = row.split(' ')
28
-
29
- percent_usage = (row[4].gsub('%', '').to_f / 100).round(2)
30
- row = {
31
- mount: row[5],
32
- fs: row[0],
33
- size: row[1],
34
- used: row[2],
35
- available: row[3],
36
- percent: percent_usage,
37
- high_usage_exceeded: percent_usage >= @@high_usage_treshold
38
- }
39
-
40
- max_percent = row[:percent] if row[:percent] > max_percent
41
-
42
- row
43
- end
44
-
45
- {
46
- mounts: df.uniq,
47
- high_usage_exceeded: max_percent >= @@high_usage_treshold,
48
- high_usage: max_percent
49
- }
50
- end
51
- end
52
- end
53
- end
1
+ require 'simple_backup/utils/disk_usage'
2
+ require 'simple_backup/utils/logger'
3
+ require 'simple_backup/utils/mailer'
4
+ require 'simple_backup/utils/mysql'
@@ -1,8 +1,8 @@
1
1
  module SimpleBackup
2
2
  class Version
3
3
  MAJOR = 0
4
- MINOR = 2
5
- PATCH = 1
4
+ MINOR = 3
5
+ PATCH = 0
6
6
  PRE_RELEASE = nil
7
7
 
8
8
  def self.get
data/lib/simple_backup.rb CHANGED
@@ -1,11 +1,46 @@
1
- require 'simple_backup/utils'
2
1
  require 'simple_backup/version'
3
- require 'simple_backup/logger'
2
+ require 'simple_backup/utils'
4
3
  require 'simple_backup/dsl'
5
- require 'simple_backup/storage'
4
+ require 'simple_backup/sources'
5
+ require 'simple_backup/backends'
6
6
  require 'simple_backup/engine'
7
- require 'simple_backup/mailer'
8
- require 'simple_backup/exception'
9
7
 
10
8
  module SimpleBackup
9
+ TIMESTAMP = Time.new.strftime('%Y%m%d%H%M%S')
10
+
11
+ @@status = :failed
12
+ @@logger = Utils::Logger.instance
13
+
14
+ def self.status
15
+ @@status
16
+ end
17
+
18
+ def self.run(&block)
19
+ @@logger.scope_start :info, "Backup #{TIMESTAMP} started"
20
+
21
+ engine = Engine::Engine.new
22
+ dsl = DSL.new(engine)
23
+
24
+ @@logger.scope_start :info, "Configuration"
25
+ dsl.instance_eval(&block)
26
+ @@logger.scope_end
27
+
28
+ engine.run
29
+ @@status = :succeed
30
+
31
+ @@logger.scope_end :info, "Backup #{TIMESTAMP} finished"
32
+ rescue StandardError => e
33
+ self.handle_exception(e)
34
+ ensure
35
+ engine.notify if engine
36
+ end
37
+
38
+ def self.handle_exception(e)
39
+ @@logger.error "#{e.class} => #{e.message}"
40
+ @@logger.error "Backup #{TIMESTAMP} failed"
41
+
42
+ STDERR.puts "Error @ #{Time.new.strftime('%Y-%m-%dT%H:%M:%S')}"
43
+ STDERR.puts "#{e.inspect}"
44
+ STDERR.puts e.backtrace
45
+ end
11
46
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomasz Maczukin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-17 00:00:00.000000000 Z
11
+ date: 2015-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -80,25 +80,23 @@ files:
80
80
  - Rakefile
81
81
  - backup_example.rb
82
82
  - lib/simple_backup.rb
83
+ - lib/simple_backup/backend/abstract.rb
84
+ - lib/simple_backup/backend/local.rb
85
+ - lib/simple_backup/backends.rb
83
86
  - lib/simple_backup/dsl.rb
84
87
  - lib/simple_backup/engine.rb
85
- - lib/simple_backup/engine/abstract.rb
86
- - lib/simple_backup/engine/app_strategy/abstract.rb
87
- - lib/simple_backup/engine/app_strategy/bare.rb
88
- - lib/simple_backup/engine/app_strategy/capistrano.rb
89
- - lib/simple_backup/engine/app_strategy/factory.rb
90
- - lib/simple_backup/engine/apps.rb
91
- - lib/simple_backup/engine/mysql.rb
92
- - lib/simple_backup/exception.rb
93
- - lib/simple_backup/exception/app_already_defined.rb
94
- - lib/simple_backup/exception/apps_dir_does_not_exists.rb
95
- - lib/simple_backup/exception/base.rb
96
- - lib/simple_backup/exception/cant_create_dir.rb
97
- - lib/simple_backup/exception/type_does_not_exists.rb
98
- - lib/simple_backup/logger.rb
99
- - lib/simple_backup/mailer.rb
100
- - lib/simple_backup/storage.rb
88
+ - lib/simple_backup/source/abstract.rb
89
+ - lib/simple_backup/source/dir.rb
90
+ - lib/simple_backup/source/dir_strategy/bare.rb
91
+ - lib/simple_backup/source/dir_strategy/capistrano.rb
92
+ - lib/simple_backup/source/file.rb
93
+ - lib/simple_backup/source/mysql.rb
94
+ - lib/simple_backup/sources.rb
101
95
  - lib/simple_backup/utils.rb
96
+ - lib/simple_backup/utils/disk_usage.rb
97
+ - lib/simple_backup/utils/logger.rb
98
+ - lib/simple_backup/utils/mailer.rb
99
+ - lib/simple_backup/utils/mysql.rb
102
100
  - lib/simple_backup/version.rb
103
101
  - simple_backup.gemspec
104
102
  homepage: https://github.com/tmaczukin/simple_backup
@@ -121,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
119
  version: '0'
122
120
  requirements: []
123
121
  rubyforge_project:
124
- rubygems_version: 2.4.5
122
+ rubygems_version: 2.2.2
125
123
  signing_key:
126
124
  specification_version: 4
127
125
  summary: Backup tool with simple DSL for configuration
@@ -1,42 +0,0 @@
1
- module SimpleBackup
2
- module Engine
3
- class Abstract
4
- @keep_last = 5
5
-
6
- def storage=(storage)
7
- @storage = storage.space(self.class.name.split('::').last)
8
- end
9
-
10
- def keep_last(value)
11
- @keep_last = value.to_i
12
- @keep_last = 5 unless @keep_last > 0
13
- end
14
-
15
- def cleanup
16
- path = @storage.dir.path
17
-
18
- backups = Dir.glob(File.join(path, '**/*.tar.gz')).map do |file|
19
- file if file.match(/.*\.tar\.gz/)
20
- end.compact.sort
21
-
22
- to_persist = backups
23
- to_persist = backups.slice(@keep_last * -1, @keep_last) if backups.length > @keep_last
24
- to_remove = backups - to_persist
25
-
26
- to_remove.each do |file|
27
- Logger::info "Removing old backup #{file}"
28
- FileUtils.rm(file)
29
- end
30
- end
31
-
32
- def sources
33
- raise NotImplementedError
34
- end
35
-
36
- def backup
37
- raise NotImplementedError
38
- end
39
- end
40
- end
41
- end
42
-
@@ -1,13 +0,0 @@
1
- module SimpleBackup
2
- module Engine
3
- module AppStrategy
4
- class Abstract
5
- attr_accessor :storage
6
-
7
- def backup(name, path, attr)
8
- raise NotImplementedError
9
- end
10
- end
11
- end
12
- end
13
- end