syc-backup 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/README.rdoc +48 -0
  2. data/Rakefile +7 -0
  3. data/bin/sycbackup +6 -0
  4. data/doc/Backup.html +186 -0
  5. data/doc/Backup/CronEdit.html +381 -0
  6. data/doc/Backup/Environment.html +231 -0
  7. data/doc/Backup/FileBackup.html +363 -0
  8. data/doc/Backup/MySQLBackup.html +305 -0
  9. data/doc/Backup/Options.html +328 -0
  10. data/doc/Backup/Process.html +261 -0
  11. data/doc/Backup/Runner.html +244 -0
  12. data/doc/README_rdoc.html +202 -0
  13. data/doc/Rakefile.html +118 -0
  14. data/doc/TestCronEdit.html +211 -0
  15. data/doc/TestEnvironment.html +156 -0
  16. data/doc/TestFileBackup.html +237 -0
  17. data/doc/TestMySQLBackup.html +167 -0
  18. data/doc/TestOptions.html +156 -0
  19. data/doc/TestProcess.html +236 -0
  20. data/doc/created.rid +18 -0
  21. data/doc/images/add.png +0 -0
  22. data/doc/images/brick.png +0 -0
  23. data/doc/images/brick_link.png +0 -0
  24. data/doc/images/bug.png +0 -0
  25. data/doc/images/bullet_black.png +0 -0
  26. data/doc/images/bullet_toggle_minus.png +0 -0
  27. data/doc/images/bullet_toggle_plus.png +0 -0
  28. data/doc/images/date.png +0 -0
  29. data/doc/images/delete.png +0 -0
  30. data/doc/images/find.png +0 -0
  31. data/doc/images/loadingAnimation.gif +0 -0
  32. data/doc/images/macFFBgHack.png +0 -0
  33. data/doc/images/package.png +0 -0
  34. data/doc/images/page_green.png +0 -0
  35. data/doc/images/page_white_text.png +0 -0
  36. data/doc/images/page_white_width.png +0 -0
  37. data/doc/images/plugin.png +0 -0
  38. data/doc/images/ruby.png +0 -0
  39. data/doc/images/tag_blue.png +0 -0
  40. data/doc/images/tag_green.png +0 -0
  41. data/doc/images/transparent.png +0 -0
  42. data/doc/images/wrench.png +0 -0
  43. data/doc/images/wrench_orange.png +0 -0
  44. data/doc/images/zoom.png +0 -0
  45. data/doc/index.html +106 -0
  46. data/doc/js/darkfish.js +153 -0
  47. data/doc/js/jquery.js +18 -0
  48. data/doc/js/navigation.js +142 -0
  49. data/doc/js/search.js +94 -0
  50. data/doc/js/search_index.js +1 -0
  51. data/doc/js/searcher.js +228 -0
  52. data/doc/rdoc.css +543 -0
  53. data/doc/table_of_contents.html +148 -0
  54. data/lib/backup/cron_edit.rb +127 -0
  55. data/lib/backup/environment.rb +44 -0
  56. data/lib/backup/file_backup.rb +94 -0
  57. data/lib/backup/mysql_backup.rb +58 -0
  58. data/lib/backup/options.rb +199 -0
  59. data/lib/backup/process.rb +99 -0
  60. data/lib/backup/runner.rb +79 -0
  61. data/lib/backup_version.rb +9 -0
  62. data/syc-backup-0.0.1.gem +0 -0
  63. data/syc-backup-0.0.3.gem +0 -0
  64. data/sycbackup.gemspec +20 -0
  65. data/test/test_cron_edit.rb +49 -0
  66. data/test/test_environment.rb +22 -0
  67. data/test/test_file_backup.rb +70 -0
  68. data/test/test_mysql_backup.rb +71 -0
  69. data/test/test_options.rb +189 -0
  70. data/test/test_process.rb +40 -0
  71. metadata +123 -0
@@ -0,0 +1,199 @@
1
+ require 'optparse'
2
+ require_relative '../backup_version'
3
+
4
+ module Backup
5
+
6
+ # Parses the command line options the user has provided on the command line
7
+ class Options
8
+ # If the user doesn't provide a backup folder a default folder is used
9
+ DEFAULT_BACKUP_FOLDER = File.expand_path("~/backup/")
10
+
11
+ # Retrieve the database name that has to be backed up
12
+ attr_reader :database
13
+ # The user that is allowed to access the database
14
+ attr_reader :user
15
+ # The user's password to access the database
16
+ attr_reader :password
17
+ # The files to be backed up
18
+ attr_reader :files
19
+ # The backup directory where the files to be backed up
20
+ attr_reader :backup_folder
21
+ # Determines whether the backup folder to be overridden when already exists
22
+ attr_reader :override
23
+ # The cron schedule
24
+ attr_reader :cron
25
+ # Determines whether to compress the backup if not to compress it returns
26
+ # false, otherwise true
27
+ attr_reader :no_compress
28
+
29
+ # Takes the arguments from the command line and parses them
30
+ def initialize(argv)
31
+ @exit_code = 0
32
+ init_exit_messages
33
+ parse(argv)
34
+ end
35
+
36
+ private
37
+
38
+ # Initializes the error messages belonging to the error codes
39
+ def init_exit_messages
40
+ @exit_message = {"1" => "Database missing",
41
+ "2" => "User missing",
42
+ "4" => "Password missing",
43
+ "8" => "Invalid cron data",
44
+ "16" => "Missing database or files"}
45
+ end
46
+
47
+ # Checks the values provided in the array c whether they are valid cron
48
+ # values representing one of the values minute, hour, day of month,
49
+ # month or day of week. Allowed values are
50
+ # minute 0..59, *
51
+ # hour 0..23, *
52
+ # day of month 1..31, *
53
+ # month 1..12, *
54
+ # day of week 1..7 , *
55
+ # If invalid values are detected bit 3 of exit_code is set and nil is
56
+ # returned. Otherwise a cron time string like "30 3 * * *" is returned.
57
+ def validate_cron_values(c)
58
+ cron_values = [0..59, 0..23, 1..31, 1..12, 1..7]
59
+ c.each.with_index do |v,i|
60
+ v.split(/,/).each do |s|
61
+ unless cron_values[i].cover?(s.to_i) or s == '*'
62
+ @exit_code |= 0b1000
63
+ return nil
64
+ end
65
+ end
66
+ end
67
+ c.fill('*', c.size..4)
68
+ c.join(' ')
69
+ end
70
+
71
+ # Check if all requested arguments are provided. If arguments are missing a
72
+ # exit code not equal to 0 is set. Following exit codes are set
73
+ # Argument Exit code
74
+ # database 00001
75
+ # user 00010
76
+ # password 00100
77
+ # cron 01000
78
+ # files and database 10000
79
+ # If for instance the database and the user is missing the exit code will
80
+ # return 3. And can be caught with
81
+ # begin
82
+ # opts = Options.new(ARGV)
83
+ # rescue ExitStatus => e
84
+ # puts "database missing" if e.status == 1
85
+ # puts "user missing" if e.status == 2
86
+ # puts "password missing" if e.status == 4
87
+ # puts "cron invalid" if e.status == 8
88
+ # puts "ether database or files required" if e.status == 16
89
+ # end
90
+ def check_for_missing_arguments
91
+ if not @database and not @files
92
+ @exit_code |= 0b10000
93
+ elsif @database or @user or @password
94
+ @exit_code |= 0b00001 unless @database
95
+ @exit_code |= 0b00010 unless @user
96
+ @exit_code |= 0b00100 unless @password
97
+ end
98
+ end
99
+
100
+ # Initializes values as the backup folder with a default value if not
101
+ # provided by the user
102
+ def initialize_default_arguments_if_missing
103
+ @backup_folder = DEFAULT_BACKUP_FOLDER unless @backup_folder
104
+ unless @override
105
+ timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
106
+ if @backup_folder and File.exists?(@backup_folder)
107
+ @backup_folder = File.dirname(@backup_folder) + '/' +
108
+ File.basename(@backup_folder) + '_' + timestamp
109
+ end
110
+ end
111
+ @backup_folder = File.expand_path(@backup_folder)
112
+ @backup_folder += '/' unless @backup_folder.match(/.*\/\Z/)
113
+ end
114
+
115
+ # Parses the user input and initializes the application
116
+ def parse(argv)
117
+
118
+ OptionParser.new do |opts|
119
+ app_name = File.basename($PROGRAM_NAME)
120
+ opts.banner = "Backup files and a database to a backup folder\n\n"+
121
+ "Usage: #{app_name} [options] [backup_folder]"
122
+
123
+ opts.on("-d", "--database DATABASE", "Database to backup") do |d|
124
+ @database = d
125
+ end
126
+
127
+ opts.on("-u", "--user USER", "User of the database") do |u|
128
+ @user = u
129
+ end
130
+
131
+ opts.on("-p", "--password PASSWORD",
132
+ "User's password to access the database") do |p|
133
+ @password = p
134
+ end
135
+
136
+ opts.on("-f", "--file f1,f2,f3", Array,
137
+ "A list of files to backup") do |f|
138
+ @files = f.map {|f| f.strip}
139
+ end
140
+
141
+ opts.on("--no-compress",
142
+ "Do not compress the backed up files",
143
+ "and database") do |n|
144
+ @no_compress = true
145
+ end
146
+
147
+ opts.on("--override", "Override the backup folder if it exists") do |o|
148
+ @override = true
149
+ end
150
+
151
+ opts.on("--cron 'm h dom m dow'", String,
152
+ "Create a cron job that automatically ",
153
+ "invokes #{app_name}",
154
+ "m = minute 0..59",
155
+ "h = hour 0..23",
156
+ "dom = day of month 1..31",
157
+ "m = month 1..12",
158
+ "dow = day of week 1..7",
159
+ "30 3 * * * will run the cron job at 3:30am") do |c|
160
+ @cron = validate_cron_values c.split(/ /).slice(0..4)
161
+ end
162
+
163
+ opts.on("-v", "--version", "Show version") do |v|
164
+ puts Backup::VERSION
165
+ exit 0
166
+ end
167
+
168
+ opts.on("-h", "--help", "Show this message") do |h|
169
+ puts opts
170
+ exit 0
171
+ end
172
+
173
+ begin
174
+ opts.parse!(argv)
175
+ rescue OptionParser::ParseError => e
176
+ STDERR.puts e.message, "\n", opts
177
+ exit(-1)
178
+ end
179
+
180
+ @backup_folder = argv.shift
181
+
182
+ check_for_missing_arguments
183
+
184
+ @exit_code.size.times do |i|
185
+ result = @exit_code & 0b00001 << i
186
+ STDERR.puts @exit_message[result.to_s] if result > 0
187
+ end
188
+
189
+ if @exit_code > 0
190
+ puts opts
191
+ exit(@exit_code)
192
+ end
193
+
194
+ initialize_default_arguments_if_missing
195
+ end
196
+
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,99 @@
1
+ require 'open3'
2
+ require 'fileutils'
3
+
4
+ # The module Backup provides functions to backup MySQL databases and files and
5
+ # folders. The backup will be compressed to a default or a specified backup
6
+ # folder. Rather than backing up the files a cron job can be scheduled
7
+ # invoking the provided command.
8
+ module Backup
9
+
10
+ # Conducts the backups of a MySQL database and files.
11
+ class Process
12
+
13
+ # Takes the backup_folder where the files are backed up to. If override is
14
+ # provided the files in the backup folder are overridden. no_compress will
15
+ # prevent compressing the backed up files and will just copy them to the
16
+ # provided backup folder
17
+ def initialize(backup_folder, files, override, no_compress)
18
+ @backup_folder = backup_folder
19
+ @files = files
20
+ @override = override
21
+ @no_compress = no_compress
22
+ end
23
+
24
+ # Creates the backup of the database and the files. If at least one of the
25
+ # provided files doesn't exist the application will print an error message
26
+ # with the inexistent files and terminates
27
+ def backup
28
+ inexistent_files = check_for_inexistent_files
29
+ unless inexistent_files.empty?
30
+ STDERR.puts "Cannot backup inexistent files"
31
+ STDERR.puts inexistent_files.join(" ")
32
+ exit 1
33
+ end
34
+
35
+ FileUtils.mkdir_p @backup_folder unless File.exists? @backup_folder
36
+
37
+ if @no_compress
38
+ copy_files
39
+ else
40
+ compress_files_and_copy
41
+ end
42
+
43
+ end
44
+
45
+ private
46
+
47
+ # Checks if files to backup have been provided that don't exist. Returns the
48
+ # inexistent files
49
+ def check_for_inexistent_files
50
+ inexistent_files = []
51
+ @files.each do |file|
52
+ inexistent_files << file unless File.exists? file
53
+ end
54
+
55
+ inexistent_files
56
+ end
57
+
58
+ # Copies the files to the backup folder. Is only invoked if --no-compress
59
+ # is provided
60
+ def copy_files
61
+ @files.each do |file|
62
+ basename = File.basename file
63
+ FileUtils.cp file, @backup_folder + basename if File.file? file
64
+ FileUtils.cp_r file, @backup_folder + basename if File.directory? file
65
+ end
66
+ end
67
+
68
+ # Compresses the files and copies the compressed file to the backup folder.
69
+ # The compression is done with
70
+ #
71
+ # tar cfz backup_folder/YYYYmmdd-HHMMSS_syc-backup.tar.gz
72
+ #
73
+ # If an error occurs while compressing the error message of tar is
74
+ # displayed and the application exits. If the method runs without errors
75
+ # the tar file is returned
76
+ def compress_files_and_copy
77
+ timestamp = ""
78
+ unless @override
79
+ timestamp = Time.now.strftime("%Y%m%d-%H%M%S") + '_'
80
+ end
81
+ tar_file = @backup_folder + timestamp + "syc-backup.tar.gz"
82
+ tar_command = "tar cfz #{tar_file} #{@files.join(" ")}"
83
+
84
+ stdout, stderr, status = Open3.capture3(tar_command)
85
+
86
+ unless status.exitstatus == 0
87
+ STDERR.puts "There was a problem executing command"
88
+ STDERR.puts tar_command
89
+ STDERR.puts stderr
90
+ exit status.exitstatus
91
+ end
92
+
93
+ tar_file
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+
@@ -0,0 +1,79 @@
1
+ require_relative 'options'
2
+ require_relative 'mysql_backup'
3
+ require_relative 'cron_edit'
4
+ require_relative 'process'
5
+ require_relative 'environment'
6
+
7
+ module Backup
8
+
9
+ # Is invoked from the command line application and invokes the application's
10
+ # optons as provided by the user on the command line
11
+ class Runner
12
+
13
+ # Takes the command line options and parses them
14
+ def initialize(argv)
15
+ @options = Options.new(argv)
16
+ end
17
+
18
+ # Operates on the options and invokes the respective functions
19
+ def run
20
+ if @options.cron
21
+ create_cron
22
+ else
23
+ create_backup
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ # Creates a cron job based on the provided options and the schedule
30
+ # provided with the --cron flag
31
+ def create_cron
32
+ cron_edit = CronEdit.new
33
+ command = cron_edit.add_command create_cron_command, Environment.ruby
34
+ puts "--> added command to cron"
35
+ puts " #{command}"
36
+ end
37
+
38
+ # Creates the cron command based on the options provided by the user.
39
+ def create_cron_command
40
+ command = @options.cron + " sycbackup #{@options.backup_folder}"
41
+ command += ' -d ' + @options.database +
42
+ ' -u' + @options.user +
43
+ ' -p' + @options.password if @options.database
44
+ command += ' -f ' + @options.files.join(',') if @options.files
45
+ command += ' --no-compress' if @options.no_compress
46
+ command += ' --override' if @options.override
47
+
48
+ command
49
+ end
50
+
51
+ # Backs up the files provided by the user on the command line. Provides a
52
+ # summary of the backed up files
53
+ def create_backup
54
+ files = []
55
+ if @options.database
56
+ db_backup = MySQLBackup.new(@options.database,
57
+ @options.user,
58
+ @options.password)
59
+ files << db_backup.backup
60
+ end
61
+
62
+ files << @options.files if @options.files
63
+
64
+ files.flatten!
65
+
66
+ process = Process.new(@options.backup_folder,
67
+ files,
68
+ @options.override,
69
+ @options.no_compress)
70
+ process.backup
71
+ puts "--> backed up files"
72
+ puts " #{files.join("\n ")}"
73
+ puts "--> to #{@options.backup_folder}"
74
+
75
+ File.delete files[0] if @options.database
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,9 @@
1
+ # Backup contains functions to backup a MySQL database and files and directories
2
+ # to a default or specified backup directory. Instead of instant backup the
3
+ # invoked command can be added to a crontab and invoked based on the provided
4
+ # schedule that is a parameter of the --cron option. The backed up files are
5
+ # per default compressed but this can be ommitted
6
+ module Backup
7
+ # Version of the application
8
+ VERSION = '0.0.4'
9
+ end
Binary file
Binary file
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.push File.expand_path("../lib", __FILE__)
2
+ require 'backup_version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "syc-backup"
6
+ s.summary = %q{Back up a database and files}
7
+ s.description = %q{Back up a database and files or schedule cron job
8
+ for backup}
9
+ s.requirements = ['No requirements']
10
+ s.version = Backup::VERSION
11
+ s.author = "Pierre Sugar"
12
+ s.email = "pierre@sugaryourcoffee.de"
13
+ s.homepage = "http://syc.dyndns.org/drupal"
14
+ s.platform = Gem::Platform::RUBY
15
+ s.required_ruby_version = '>=1.9'
16
+ s.files = Dir['**/**']
17
+ s.executables = ['sycbackup']
18
+ s.test_files = Dir['test/test*.rb']
19
+ s.has_rdoc = true
20
+ end
@@ -0,0 +1,49 @@
1
+ require 'test/unit'
2
+ require 'shoulda'
3
+ require_relative '../lib/backup/cron_edit.rb'
4
+
5
+ # Test for CronEdit will add commands to the users crontab. After each test
6
+ # the test entries are removed. So the crontab should not be changed after a
7
+ # test.
8
+ # In case of changes to CronEdit that will lead to a system exit it might leave
9
+ # the crontab with test data. In that case you have to remove the test data
10
+ # manually. But subsequent successfull runs should remove all test entries.
11
+ class TestCronEdit < Test::Unit::TestCase
12
+
13
+ context "Crontab operation" do
14
+
15
+ # Determines the count of the provided _command_ in the crontab.
16
+ def crontab_count(command)
17
+ content = `crontab -l`
18
+ count = 0
19
+ content.split(/\n/).each do |c|
20
+ count += 1 if c == command
21
+ end
22
+ count
23
+ end
24
+
25
+ # Adds a command to crontab and checks that it is entered. Removes command
26
+ # after test.
27
+ should "add command to crontab" do
28
+ ce = Backup::CronEdit.new
29
+ command = "1 2 * * * ls -l"
30
+ ce.add_command command
31
+ assert_equal 1, crontab_count(command)
32
+ ce.remove_command command
33
+ assert_equal 0, crontab_count(command)
34
+ end
35
+
36
+ # Adds a command twice to crontab and expects that it is contained only
37
+ # once. Removes added command at end of test
38
+ should "not add duplicate command to crontab" do
39
+ ce = Backup::CronEdit.new
40
+ command = "2 2 2 * * syc-backup -d test -upierre -ppass"
41
+ ce.add_command command
42
+ assert_equal 1, crontab_count(command)
43
+ ce.add_command command
44
+ assert_equal 1, crontab_count(command)
45
+ ce.remove_command command
46
+ assert_equal 0, crontab_count(command)
47
+ end
48
+ end
49
+ end