syc-backup 0.0.4

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 (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