sweety_backy 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .rvmrc
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
data/.rvmrc_example ADDED
@@ -0,0 +1 @@
1
+ rvm 1.8.7@sweety_backy
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sweety_backy.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # Sweety Backy
2
+
3
+ Simple mechanism to **configure and execute backups** of folders and MySQL DBs and store them in **local folder** or **S3 bucket**.
4
+
5
+ ## State
6
+
7
+ This is a **really beta** version which is working in my servers actually without problems but you have to use it under your own risk.
8
+
9
+ ## Other possibilities
10
+
11
+ Please take a look of other **Ruby backups gems**:
12
+
13
+ * http://ruby-toolbox.com/categories/backups.html
14
+
15
+ ## How install
16
+
17
+ gem install 'sweety_backy'
18
+
19
+ ## How to use it
20
+
21
+ sweety_backy <config_file>
22
+
23
+ ### Config file
24
+
25
+ It is a _yaml_ file with all this attributes
26
+
27
+ paths: <array of folder paths>
28
+ databases: <array of database names>
29
+ yearly: <quantity of yearly backups>
30
+ monthly: <quantity of monthly backups>
31
+ weekly: <quantity of weekly backups>
32
+ daily: <quantity of daily backups>
33
+ database_user: <database user with read privileges of all datases>
34
+ database_pass: <database user password>
35
+ storage_system: { 's3' | 'local' }
36
+ local_opts: (only if the storage_system is 'local')
37
+ path: <absoulte path to the root folder of the backups>
38
+ s3_opts: (only if the storage_system is 's3')
39
+ bucket: <bucket name>
40
+ path: <bucket path where the backups will be stored><
41
+ passwd_file: <path to the S3 credentials>
42
+
43
+ ### S3 credentials file
44
+
45
+ It is a _yaml_ file with two keys with the S3 credentials:
46
+
47
+ access_key_id: "XXX"
48
+ secret_access_key: "YYY"
49
+
50
+ ### Example
51
+
52
+ #### S3 config example
53
+
54
+ # ~/.s3.passwd
55
+ access_key_id: "XXX"
56
+ secret_access_key: "YYY"
57
+
58
+
59
+ #### SweetyBacky config example
60
+
61
+ # ~/.sweety_backy.conf
62
+ paths: [ "/Users/fguillen/Develop/Brico", "/Users/fguillen/Develop/Arduino" ]
63
+ databases: [ "test", "mysql" ]
64
+ yearly: 1
65
+ monthly: 2
66
+ weekly: 3
67
+ daily: 4
68
+ database_user: 'root'
69
+ database_pass: ''
70
+ storage_system: 's3'
71
+ s3_opts:
72
+ bucket: 'sweety_backy'
73
+ path: 'fguillen'
74
+ passwd_file: '~/.s3.passwd'
75
+
76
+ #### Execute
77
+
78
+ sweety_backy ~/.sweety_backy.conf
79
+
80
+ #### Result
81
+
82
+ This will generate a bunch of backups in the _sweety_backy_ bucket like these ones:
83
+
84
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Arduino.20110626.weekly.tar.gz
85
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Arduino.20110703.weekly.tar.gz
86
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Arduino.20110704.daily.tar.gz
87
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Arduino.20110705.daily.tar.gz
88
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Arduino.20110706.daily.tar.gz
89
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Arduino.20110707.daily.tar.gz
90
+
91
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Brico.20110626.weekly.tar.gz
92
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Brico.20110703.weekly.tar.gz
93
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Brico.20110704.daily.tar.gz
94
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Brico.20110705.daily.tar.gz
95
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Brico.20110706.daily.tar.gz
96
+ https://s3.amazonaws.com/sweety_backy/fguillen/files/Users.fguillen.Develop.Brico.20110707.daily.tar.gz
97
+
98
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/test.20110626.weekly.sql.tar.gz
99
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/test.20110703.weekly.sql.tar.gz
100
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/test.20110704.daily.sql.tar.gz
101
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/test.20110705.daily.sql.tar.gz
102
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/test.20110706.daily.sql.tar.gz
103
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/test.20110707.daily.sql.tar.gz
104
+
105
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/mysql.20110626.weekly.sql.tar.gz
106
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/mysql.20110703.weekly.sql.tar.gz
107
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/mysql.20110704.daily.sql.tar.gz
108
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/mysql.20110705.daily.sql.tar.gz
109
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/mysql.20110706.daily.sql.tar.gz
110
+ https://s3.amazonaws.com/sweety_backy/fguillen/databases/mysql.20110707.daily.sql.tar.gz
111
+
112
+ ... and so on.
113
+
114
+ ### Cron execution example
115
+
116
+ # every day at 02:00 am
117
+ 00 02 * * * sweety_backy /home/fguillen/.sweety_backy.conf >> /var/log/sweety_backy.log 2>&1
118
+
119
+ ## License
120
+
121
+ MIT License. (c) 2011 Fernando Guillen (http://fernandoguillen.info).
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ # require 'bundler/gem_tasks'
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'bundler'
6
+
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ task :default => :test
10
+
11
+ Rake::TestTask.new do |t|
12
+ t.libs << '.'
13
+ t.test_files = FileList['test/*_test.rb']
14
+ t.verbose = true
15
+ end
16
+
17
+ namespace :test do
18
+
19
+ desc "run s3 test in real"
20
+ task :s3 do
21
+ test_task =
22
+ Rake::TestTask.new("s3_tests") do |t|
23
+ t.libs << '.'
24
+ t.test_files = FileList['test/s3/*_test.rb']
25
+ t.verbose = true
26
+ end
27
+
28
+ task("s3_tests").execute
29
+ end
30
+
31
+ end
data/bin/sweety_backy ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Use:
4
+ # sweety_backy /path/to/sweety_backy.conf
5
+
6
+ begin
7
+ require 'sweety_backy'
8
+ rescue LoadError
9
+ require 'rubygems'
10
+ require 'sweety_backy'
11
+ end
12
+
13
+ require 'benchmark'
14
+
15
+ if( ARGV[0].nil? )
16
+ SweetyBacky::Utils.log "use: $ sweety_backy <config_file_path>"
17
+ exit 1
18
+ end
19
+
20
+ lapsus_time =
21
+ Benchmark.realtime do
22
+ SweetyBacky::Utils.log "--------------------"
23
+ SweetyBacky::Utils.log "Starting SweetyBacky"
24
+ sb = SweetyBacky::Runner.new( ARGV[0] )
25
+ sb.run
26
+ end
27
+
28
+ SweetyBacky::Utils.log "SweetyBacky on #{lapsus_time} seconds"
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require "s3"
3
+
4
+ require "#{File.dirname(__FILE__)}/sweety_backy/version"
5
+ require "#{File.dirname(__FILE__)}/sweety_backy/runner"
6
+ require "#{File.dirname(__FILE__)}/sweety_backy/utils"
7
+ require "#{File.dirname(__FILE__)}/sweety_backy/commander"
8
+ require "#{File.dirname(__FILE__)}/sweety_backy/s3"
9
+ require "#{File.dirname(__FILE__)}/sweety_backy/opts_reader"
@@ -0,0 +1,82 @@
1
+ module SweetyBacky
2
+ module Commander
3
+ def self.do_files_backup( path, backup_path, opts )
4
+ SweetyBacky::Utils.log "doing files backup of #{path} to #{backup_path}"
5
+
6
+ FileUtils.mkdir_p( File.dirname( backup_path ) )
7
+ SweetyBacky::Utils::command( "tar -cz --directory #{path} --same-permissions --file #{backup_path} ." )
8
+ end
9
+
10
+ def self.do_database_backup( database_name, backup_path, opts )
11
+ SweetyBacky::Utils.log "doing database backup #{database_name} on #{backup_path}"
12
+
13
+ FileUtils.mkdir_p( File.dirname( backup_path ) )
14
+ tmp_sql_file_path = File.join( Dir::tmpdir, "#{File.basename( backup_path, '.tar.gz' )}" )
15
+
16
+ database_pass = opts[:database_pass].empty? ? '' : "-p#{opts[:database_pass]}"
17
+
18
+ SweetyBacky::Utils::command( "mysqldump -u#{opts[:database_user]} #{opts[:database_pass]} #{database_name} > #{tmp_sql_file_path}" )
19
+ SweetyBacky::Utils::command( "tar -cz --same-permissions --file #{backup_path} --directory #{File.dirname(tmp_sql_file_path)} #{File.basename(tmp_sql_file_path)}" )
20
+
21
+ File.delete( tmp_sql_file_path )
22
+ end
23
+
24
+ def self.clear( opts )
25
+ clear_files( opts )
26
+ clear_databases( opts )
27
+ end
28
+
29
+ def self.clear_files( opts )
30
+ SweetyBacky::Utils.log "cleaning files on #{opts[:working_path]}/files/"
31
+
32
+ opts[:paths].each do |path|
33
+ SweetyBacky::Utils.log "cleaning file #{path}"
34
+
35
+ [:yearly, :monthly, :weekly, :daily].each do |period|
36
+ paths_in(
37
+ "#{opts[:working_path]}/files/#{SweetyBacky::Utils.namerize( path )}.*.#{period.to_s}.*",
38
+ opts
39
+ ).sort[0..(-1*(opts[period]+1))].each do |file_path|
40
+ SweetyBacky::Utils.log "removing: #{file_path}"
41
+ remove_path( file_path, opts )
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.clear_databases( opts )
48
+ SweetyBacky::Utils.log "cleaning databases on #{opts[:working_path]}/databases/"
49
+
50
+ opts[:databases].each do |database_name|
51
+ SweetyBacky::Utils.log "cleaning database #{database_name}"
52
+
53
+ [:yearly, :monthly, :weekly, :daily].each do |period|
54
+ paths_in(
55
+ "#{opts[:working_path]}/databases/#{database_name}.*.#{period.to_s}.*",
56
+ opts
57
+ ).sort[0..(-1*(opts[period]+1))].each do |file_path|
58
+ SweetyBacky::Utils.log "removing: #{file_path}"
59
+ remove_path( file_path, opts )
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def self.paths_in( path, opts )
66
+ if( opts[:storage_system].to_sym == :s3 )
67
+ return SweetyBacky::S3.paths_in( path, opts[:s3_opts] )
68
+ else
69
+ return Dir.glob( path )
70
+ end
71
+ end
72
+
73
+ def self.remove_path( path, opts )
74
+ if( opts[:storage_system].to_sym == :s3 )
75
+ SweetyBacky::S3.delete( path, opts[:s3_opts] )
76
+ else
77
+ File.delete( path )
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,47 @@
1
+ module SweetyBacky
2
+ module OptsReader
3
+ def self.read_opts( conf_path )
4
+ SweetyBacky::Utils::log "conf_path: #{conf_path}"
5
+
6
+ opts = YAML.load( File.read( conf_path ) )
7
+ new_opts = {}
8
+
9
+ # symbolize keys
10
+ opts.keys.each do |key|
11
+
12
+ if( opts[key].is_a? Hash )
13
+ new_opts[key.to_sym] = {}
14
+ opts[key].keys.each do |key2|
15
+ new_opts[key.to_sym][key2.to_sym] = opts[key][key2]
16
+ end
17
+ else
18
+ new_opts[key.to_sym] = opts[key]
19
+ end
20
+
21
+ end
22
+
23
+ log_configuration( new_opts )
24
+
25
+ # TODO: test all options are ok
26
+
27
+ return new_opts
28
+ end
29
+
30
+ def self.log_configuration( opts )
31
+ SweetyBacky::Utils::log "configuration:"
32
+ SweetyBacky::Utils::log "------------"
33
+ opts.each_pair do |key, value|
34
+ if( value.is_a? Array )
35
+ SweetyBacky::Utils::log "#{key}: #{value.join(' | ')}"
36
+ elsif( value.is_a? Hash )
37
+ value.each_pair do |key2, value2|
38
+ SweetyBacky::Utils::log "#{key} => #{key2}: #{value2}"
39
+ end
40
+ else
41
+ SweetyBacky::Utils::log "#{key}: #{value}"
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,88 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+ require 'tmpdir'
4
+ require 'yaml'
5
+ require File.dirname(__FILE__) + "/utils.rb"
6
+
7
+ module SweetyBacky
8
+ class Runner
9
+ attr_reader :opts
10
+
11
+ def initialize( path = nil )
12
+ if( !path.nil? )
13
+ config( SweetyBacky::OptsReader.read_opts( path ) )
14
+ end
15
+ end
16
+
17
+ def config( opts )
18
+ @opts = {
19
+ :paths => [],
20
+ :databases => [],
21
+ :yearly => 1,
22
+ :monthly => 1,
23
+ :weekly => 2,
24
+ :daily => 4,
25
+ :storage_system => :local
26
+ }.merge( opts )
27
+
28
+ if( @opts[:storage_system].to_sym == :s3 )
29
+ @opts[:working_path] = File.join( Dir::tmpdir, "sweety_backy_#{Time.now.to_i}" )
30
+ else
31
+ @opts[:working_path] = @opts[:local_opts][:path]
32
+ end
33
+ end
34
+
35
+ def do_backup
36
+ do_files_backup
37
+ do_databases_backup
38
+ end
39
+
40
+ def do_files_backup
41
+ @opts[:paths].each do |path|
42
+ backup_path = "#{@opts[:working_path]}/files/#{SweetyBacky::Utils.namerize( path )}.#{Date.today.strftime('%Y%m%d')}.#{SweetyBacky::Utils.period}.tar.gz"
43
+ SweetyBacky::Commander.do_files_backup( path, backup_path, @opts )
44
+
45
+ if( @opts[:storage_system].to_sym == :s3 )
46
+ SweetyBacky::S3.upload(
47
+ backup_path,
48
+ "#{@opts[:s3_opts][:path]}/files/#{File.basename( backup_path )}",
49
+ @opts[:s3_opts]
50
+ )
51
+
52
+ FileUtils.rm_rf backup_path
53
+ end
54
+ end
55
+ end
56
+
57
+ def do_databases_backup
58
+ @opts[:databases].each do |database_name|
59
+ backup_path = "#{@opts[:working_path]}/databases/#{database_name}.#{Date.today.strftime('%Y%m%d')}.#{SweetyBacky::Utils.period}.sql.tar.gz"
60
+ SweetyBacky::Commander.do_database_backup( database_name, backup_path, @opts)
61
+
62
+ if( @opts[:storage_system].to_sym == :s3 )
63
+ SweetyBacky::S3.upload(
64
+ backup_path,
65
+ "#{@opts[:s3_opts][:path]}/databases/#{File.basename( backup_path )}",
66
+ @opts[:s3_opts]
67
+ )
68
+
69
+ FileUtils.rm_rf backup_path
70
+ end
71
+ end
72
+ end
73
+
74
+ def run
75
+ begin
76
+ do_backup
77
+ SweetyBacky::Commander.clear( @opts )
78
+ rescue => e
79
+ SweetyBacky::Utils.log "ERROR: #{e}"
80
+ SweetyBacky::Utils.log "BACKTRACE: #{e.backtrace.join("\n")}"
81
+ SweetyBacky::Utils.log "I should send and email at this moment"
82
+ end
83
+ end
84
+
85
+
86
+ end
87
+ end
88
+
@@ -0,0 +1,56 @@
1
+ module SweetyBacky
2
+ class S3
3
+ def self.upload( path, backup_path, opts )
4
+ SweetyBacky::Utils.log( "S3 uploading: #{path} to #{opts[:bucket]}/#{backup_path}" )
5
+
6
+ s3 = ::S3::Service.new( read_s3_password( opts[:passwd_file] ) )
7
+ bucket = s3.bucket( opts[:bucket] )
8
+
9
+ if !bucket.exists?
10
+ bucket = s3.buckets.build( opts[:bucket] )
11
+ bucket.save
12
+ end
13
+
14
+ object = bucket.objects.build( backup_path )
15
+ object.content = File.open( path )
16
+ object.save
17
+ end
18
+
19
+ def self.object( path, opts )
20
+ s3 = ::S3::Service.new( read_s3_password( opts[:passwd_file] ) )
21
+ bucket = s3.buckets.find( opts[:bucket] )
22
+ object = bucket.objects.find( path )
23
+
24
+ return object
25
+ end
26
+
27
+ def self.paths_in( path, opts )
28
+ s3 = ::S3::Service.new( read_s3_password( opts[:passwd_file] ) )
29
+ bucket = s3.buckets.find( opts[:bucket] )
30
+
31
+ regex = Regexp.escape( path ).gsub('\*', '.*').gsub('\?', '.')
32
+
33
+ objects = bucket.objects.select { |e| e.key =~ /#{regex}/ }
34
+ paths = objects.map(&:key)
35
+
36
+ return paths
37
+ end
38
+
39
+ def self.read_s3_password( path )
40
+ opts = YAML.load( File.read( File.expand_path path ) )
41
+ new_opts = {}
42
+
43
+ # symbolize keys
44
+ opts.keys.each do |key|
45
+ new_opts[key.to_sym] = opts[key]
46
+ end
47
+
48
+ return new_opts
49
+ end
50
+
51
+ def self.delete( path, opts )
52
+ SweetyBacky::S3.object( path, opts ).destroy
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ module SweetyBacky
2
+ class Utils
3
+
4
+ def self.period
5
+ return 'yearly' if( is_last_day_of_year? )
6
+ return 'monthly' if( is_last_day_of_month? )
7
+ return 'weekly' if( is_last_day_of_week? )
8
+ return 'daily'
9
+ end
10
+
11
+ def self.is_last_day_of_year?
12
+ return Date.new(Date.today.year, 12, -1) == Date.today
13
+ end
14
+
15
+ def self.is_last_day_of_month?
16
+ return Date.new(Date.today.year, Date.today.month, -1) == Date.today
17
+ end
18
+
19
+ def self.is_last_day_of_week?
20
+ return Date.today.cwday == 7
21
+ end
22
+
23
+ def self.command( _command )
24
+ Utils.log "command: #{_command}"
25
+
26
+ result = %x( #{_command} 2>&1 )
27
+
28
+ raise "ERROR: on command: '#{_command}', result: '#{result}'" if $?.exitstatus != 0
29
+
30
+ return result
31
+ end
32
+
33
+ def self.log( msg )
34
+ puts "#{Time.now.strftime("%Y-%m-%d %H:%M")}: #{msg}"
35
+ end
36
+
37
+ def self.namerize( path )
38
+ path.gsub('/', '.').gsub(/^\./, '')
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module SweetyBacky
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "sweety_backy"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "sweety_backy"
7
+ s.version = SweetyBacky::VERSION
8
+ s.authors = ["Fernando Guillen"]
9
+ s.email = ["fguillen.mail@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = "Ruby backup mechanism"
12
+ s.description = "Simple mechanism to configure and execute backups of folders and MySQL DBs and store them in local folder or S3 bucket"
13
+
14
+ s.rubyforge_project = "SweetyBacky"
15
+
16
+ s.add_development_dependency "bundler", ">= 1.0.0.rc.6"
17
+ s.add_development_dependency "mocha"
18
+ s.add_development_dependency "delorean"
19
+
20
+ s.add_dependency "s3"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,134 @@
1
+ require "#{File.dirname(__FILE__)}/test_helper"
2
+
3
+ class CommanderTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ SweetyBacky::Utils.stubs(:log)
7
+
8
+ # tmp dir
9
+ @tmp_dir = File.join( Dir::tmpdir, "sweety_backy_#{Time.now.to_i}" )
10
+ Dir.mkdir( @tmp_dir ) unless File.exists?(@tmp_dir)
11
+ end
12
+
13
+ def teardown
14
+ FileUtils.rm_rf @tmp_dir if File.exists?(@tmp_dir)
15
+ end
16
+
17
+ def test_do_files_backup
18
+ SweetyBacky::Commander.do_files_backup(
19
+ "#{FIXTURES_PATH}/path",
20
+ "#{@tmp_dir}/back.tar.gz",
21
+ {}
22
+ )
23
+
24
+ result = %x(tar -tzvf #{@tmp_dir}/back.tar.gz)
25
+
26
+ assert_match( "./", result )
27
+ assert_match( "./file1.txt", result )
28
+ assert_match( "./a/", result )
29
+ assert_match( "./b/file3.txt", result )
30
+ end
31
+
32
+ def test_do_databases_backup
33
+ SweetyBacky::Commander.do_database_backup(
34
+ "test",
35
+ "#{@tmp_dir}/back.sql.tar.gz",
36
+ {
37
+ :database_user => "test",
38
+ :database_pass => ""
39
+ }
40
+ )
41
+
42
+ result = %x(tar -tzvf #{@tmp_dir}/back.sql.tar.gz)
43
+
44
+ assert_match( /\sback.sql$/, result )
45
+ end
46
+
47
+
48
+ def test_clear
49
+ opts = {
50
+ :paths => [ 'name1', 'name2' ],
51
+ :databases => [ 'name1', 'name2' ],
52
+ :yearly => 1,
53
+ :monthly => 2,
54
+ :weekly => 3,
55
+ :daily => 4,
56
+ :storage_system => :local,
57
+ :local_opts => {
58
+ :path => @tmp_dir
59
+ },
60
+ :working_path => @tmp_dir
61
+ }
62
+
63
+ Dir.mkdir( "#{@tmp_dir}/files" ) unless File.exists?( "#{@tmp_dir}/files" )
64
+ Dir.mkdir( "#{@tmp_dir}/databases" ) unless File.exists?( "#{@tmp_dir}/databases" )
65
+
66
+ [
67
+ 'name1.20081231.yearly',
68
+ 'name1.20081232.yearly',
69
+ 'name2.20091231.yearly',
70
+ 'name1.20100131.monthly',
71
+ 'name1.20100228.monthly',
72
+ 'name1.20100331.monthly',
73
+ 'name2.20100430.monthly',
74
+ 'name2.20100531.monthly',
75
+ 'name2.20100630.monthly',
76
+ 'name1.20100704.weekly',
77
+ 'name1.20100711.weekly',
78
+ 'name1.20100718.weekly',
79
+ 'name1.20100725.weekly',
80
+ 'name1.20100720.daily',
81
+ 'name2.20100721.daily',
82
+ 'name2.20100722.daily',
83
+ 'name2.20100723.daily',
84
+ 'name2.20100724.daily',
85
+ 'name2.20100726.daily'
86
+ ].each do |file_part|
87
+ File.open( "#{@tmp_dir}/files/#{file_part}.tar.gz", 'w' ) { |f| f.write 'wadus' }
88
+ File.open( "#{@tmp_dir}/databases/#{file_part}.sql.tar.gz", 'w' ) { |f| f.write 'wadus' }
89
+ end
90
+
91
+ # puts @tmp_dir
92
+ # exit 1
93
+
94
+ SweetyBacky::Commander.clear( opts )
95
+
96
+ files_keeped = Dir.glob( "#{@tmp_dir}/files/*" ).join( "\n" )
97
+ databases_keeped = Dir.glob( "#{@tmp_dir}/databases/*" ).join( "\n" )
98
+
99
+ # files to keep
100
+ [
101
+ 'name1.20081232.yearly',
102
+ 'name2.20091231.yearly',
103
+ 'name1.20100228.monthly',
104
+ 'name1.20100331.monthly',
105
+ 'name2.20100531.monthly',
106
+ 'name2.20100630.monthly',
107
+ 'name1.20100718.weekly',
108
+ 'name1.20100725.weekly',
109
+ 'name1.20100720.daily',
110
+ 'name2.20100722.daily',
111
+ 'name2.20100723.daily',
112
+ 'name2.20100724.daily',
113
+ 'name2.20100726.daily'
114
+ ].each do |file_part|
115
+ assert_match( "#{file_part}.tar.gz", files_keeped )
116
+ assert_match( "#{file_part}.sql.tar.gz", databases_keeped )
117
+ end
118
+
119
+ # files to deleted
120
+ [
121
+ 'name1.20081231.yearly',
122
+ 'name1.20100131.monthly',
123
+ 'name2.20100430.monthly',
124
+ 'name1.20100704.weekly',
125
+ 'name2.20100721.daily'
126
+ ].each do |file_part|
127
+ assert_no_match( /#{file_part}.tar.gz/, files_keeped )
128
+ assert_no_match( /#{file_part}.sql.tar.gz/, databases_keeped )
129
+ end
130
+ end
131
+
132
+
133
+ end
134
+
@@ -0,0 +1,13 @@
1
+ paths: [ "path1", "path2" ]
2
+ databases: [ "db1", "db2" ]
3
+ yearly: 1
4
+ monthly: 2
5
+ weekly: 3
6
+ daily: 4
7
+ database_user: 'database_user'
8
+ database_pass: 'database_pass'
9
+ storage_system: 's3'
10
+ s3_opts:
11
+ bucket: 'bucket_name'
12
+ path: 's3/path/path'
13
+ passwd_file: '/path/.s3.passwd'
@@ -0,0 +1 @@
1
+ file
@@ -0,0 +1 @@
1
+ file2
@@ -0,0 +1 @@
1
+ file3
@@ -0,0 +1 @@
1
+ file1
@@ -0,0 +1,2 @@
1
+ access_key_id: "access_key"
2
+ secret_access_key: "secret_access"
@@ -0,0 +1,25 @@
1
+ require "#{File.dirname(__FILE__)}/test_helper"
2
+
3
+ class OptsReaderTest < Test::Unit::TestCase
4
+ def setup
5
+ SweetyBacky::Utils.stubs(:log)
6
+ end
7
+
8
+ def test_read_opts
9
+ opts = SweetyBacky::OptsReader.read_opts( "#{FIXTURES_PATH}/config_s3.yml" )
10
+
11
+ assert_equal( [ "path1", "path2" ], opts[:paths] )
12
+ assert_equal( [ "db1", "db2" ], opts[:databases] )
13
+ assert_equal( 1, opts[:yearly] )
14
+ assert_equal( 2, opts[:monthly] )
15
+ assert_equal( 3, opts[:weekly] )
16
+ assert_equal( 4, opts[:daily] )
17
+ assert_equal( 'database_user', opts[:database_user] )
18
+ assert_equal( 'database_pass', opts[:database_pass] )
19
+ assert_equal( 'bucket_name', opts[:s3_opts][:bucket] )
20
+ assert_equal( 's3/path/path', opts[:s3_opts][:path] )
21
+ assert_equal( '/path/.s3.passwd', opts[:s3_opts][:passwd_file] )
22
+ end
23
+
24
+
25
+ end
@@ -0,0 +1,102 @@
1
+ require "#{File.dirname(__FILE__)}/test_helper"
2
+
3
+ class RunnerTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ SweetyBacky::Utils.stubs(:log)
7
+
8
+ # tmp dir
9
+ @tmp_dir = File.join( Dir::tmpdir, "sweety_backy_#{Time.now.to_i}" )
10
+ Dir.mkdir( @tmp_dir ) unless File.exists?(@tmp_dir)
11
+
12
+ # runner
13
+ @opts = {
14
+ :paths => [ "#{FIXTURES_PATH}/path" ],
15
+ :databases => [ "test" ],
16
+ :yearly => 1,
17
+ :monthly => 1,
18
+ :weekly => 2,
19
+ :daily => 4,
20
+ :database_user => 'test',
21
+ :database_pass => '',
22
+ :storage_system => :local,
23
+ :local_opts => {
24
+ :path => @tmp_dir
25
+ }
26
+ }
27
+
28
+ @runner = SweetyBacky::Runner.new
29
+ @runner.config( @opts )
30
+ end
31
+
32
+ def teardown
33
+ FileUtils.rm_rf @tmp_dir if File.exists?(@tmp_dir)
34
+ end
35
+
36
+ def test_do_backup_yearly
37
+ Delorean.time_travel_to( '2009-12-31' ) do
38
+ @runner.do_backup
39
+ end
40
+
41
+ assert( File.exists?( "#{@tmp_dir}/files/#{SweetyBacky::Utils.namerize( @opts[:paths][0] )}.20091231.yearly.tar.gz") )
42
+ assert( File.exists?( "#{@tmp_dir}/databases/test.20091231.yearly.sql.tar.gz") )
43
+ end
44
+
45
+ def test_do_backup_monthly
46
+ Delorean.time_travel_to( '2010-01-31' ) do
47
+ @runner.do_backup
48
+ end
49
+
50
+ assert( File.exists?( "#{@tmp_dir}/files/#{SweetyBacky::Utils.namerize( @opts[:paths][0] )}.20100131.monthly.tar.gz") )
51
+ assert( File.exists?( "#{@tmp_dir}/databases/test.20100131.monthly.sql.tar.gz") )
52
+ end
53
+
54
+ def test_do_backup_weekly
55
+ Delorean.time_travel_to( '2010-01-03' ) do
56
+ @runner.do_backup
57
+ end
58
+
59
+ assert( File.exists?( "#{@tmp_dir}/files/#{SweetyBacky::Utils.namerize( @opts[:paths][0] )}.20100103.weekly.tar.gz") )
60
+ assert( File.exists?( "#{@tmp_dir}/databases/test.20100103.weekly.sql.tar.gz") )
61
+ end
62
+
63
+ def test_do_backup_daily
64
+ Delorean.time_travel_to( '2010-01-04' ) do
65
+ @runner.do_backup
66
+ end
67
+
68
+ assert( File.exists?( "#{@tmp_dir}/files/#{SweetyBacky::Utils.namerize( @opts[:paths][0] )}.20100104.daily.tar.gz") )
69
+ assert( File.exists?( "#{@tmp_dir}/databases/test.20100104.daily.sql.tar.gz") )
70
+ end
71
+
72
+ def test_run
73
+ @runner.expects(:do_backup)
74
+ SweetyBacky::Commander.expects(:clear)
75
+
76
+ @runner.run
77
+ end
78
+
79
+ def test_initialize_with_config_file
80
+ SweetyBacky::OptsReader.expects( :read_opts ).with( '/path/config.yml' ).returns(
81
+ {
82
+ :paths => [ 'pepe', 'juan' ],
83
+ :local_opts => {
84
+ :path => '/local/path'
85
+ }
86
+ }
87
+ )
88
+
89
+ runner = SweetyBacky::Runner.new( "/path/config.yml" )
90
+
91
+ assert_equal( [ "pepe", "juan" ], runner.opts[:paths] )
92
+ assert_equal( [], runner.opts[:databases] )
93
+ assert_equal( 1, runner.opts[:yearly] )
94
+ assert_equal( 1, runner.opts[:monthly] )
95
+ assert_equal( 2, runner.opts[:weekly] )
96
+ assert_equal( 4, runner.opts[:daily] )
97
+ assert_equal( :local, runner.opts[:storage_system] )
98
+ assert_equal( '/local/path', runner.opts[:local_opts][:path] )
99
+ end
100
+
101
+ end
102
+
@@ -0,0 +1,100 @@
1
+ require "#{File.dirname(__FILE__)}/../test_helper"
2
+
3
+ class CommanderS3Test < Test::Unit::TestCase
4
+
5
+ def setup
6
+ SweetyBacky::Utils.stubs(:log)
7
+
8
+ @opts = {
9
+ :paths => [ 'name1', 'name2' ],
10
+ :databases => [ 'name1', 'name2' ],
11
+ :yearly => 1,
12
+ :monthly => 2,
13
+ :weekly => 3,
14
+ :daily => 4,
15
+ :storage_system => :s3,
16
+ :s3_opts => {
17
+ :bucket => 'sweety_backy_test',
18
+ :path => 'test/path',
19
+ :passwd_file => '~/.s3.passwd'
20
+ },
21
+ :working_path => @tmp_dir
22
+ }
23
+
24
+ s3 = ::S3::Service.new( SweetyBacky::S3.read_s3_password( @opts[:s3_opts][:passwd_file] ) )
25
+
26
+ @bucket = s3.buckets.build( @opts[:s3_opts][:bucket] )
27
+ @bucket.save
28
+ end
29
+
30
+ def teardown
31
+ @bucket.destroy( true )
32
+ end
33
+
34
+ def test_clear
35
+ [
36
+ 'name1.20081231.yearly',
37
+ 'name1.20081232.yearly',
38
+ 'name2.20091231.yearly',
39
+ 'name1.20100131.monthly',
40
+ 'name1.20100228.monthly',
41
+ 'name1.20100331.monthly',
42
+ 'name2.20100430.monthly',
43
+ 'name2.20100531.monthly',
44
+ 'name2.20100630.monthly',
45
+ 'name1.20100704.weekly',
46
+ 'name1.20100711.weekly',
47
+ 'name1.20100718.weekly',
48
+ 'name1.20100725.weekly',
49
+ 'name1.20100720.daily',
50
+ 'name2.20100721.daily',
51
+ 'name2.20100722.daily',
52
+ 'name2.20100723.daily',
53
+ 'name2.20100724.daily',
54
+ 'name2.20100726.daily'
55
+ ].each do |file_part|
56
+ SweetyBacky::S3.upload( "#{FIXTURES_PATH}/file.txt", "#{@opts[:s3_opts][:path]}/files/#{file_part}.tar.gz", @opts[:s3_opts] )
57
+ SweetyBacky::S3.upload( "#{FIXTURES_PATH}/file.txt", "#{@opts[:s3_opts][:path]}/databases/#{file_part}.sql.tar.gz", @opts[:s3_opts] )
58
+ end
59
+
60
+ SweetyBacky::Commander.clear( @opts )
61
+
62
+ files_keeped = SweetyBacky::S3.paths_in( "#{@opts[:s3_opts][:path]}/files/*", @opts[:s3_opts] ).join( "\n" )
63
+ databases_keeped = SweetyBacky::S3.paths_in( "#{@opts[:s3_opts][:path]}/databases/*", @opts[:s3_opts] ).join( "\n" )
64
+
65
+ # files to keep
66
+ [
67
+ 'name1.20081232.yearly',
68
+ 'name2.20091231.yearly',
69
+ 'name1.20100228.monthly',
70
+ 'name1.20100331.monthly',
71
+ 'name2.20100531.monthly',
72
+ 'name2.20100630.monthly',
73
+ 'name1.20100718.weekly',
74
+ 'name1.20100725.weekly',
75
+ 'name1.20100720.daily',
76
+ 'name2.20100722.daily',
77
+ 'name2.20100723.daily',
78
+ 'name2.20100724.daily',
79
+ 'name2.20100726.daily'
80
+ ].each do |file_part|
81
+ assert_match( "#{file_part}.tar.gz", files_keeped )
82
+ assert_match( "#{file_part}.sql.tar.gz", databases_keeped )
83
+ end
84
+
85
+ # files to deleted
86
+ [
87
+ 'name1.20081231.yearly',
88
+ 'name1.20100131.monthly',
89
+ 'name2.20100430.monthly',
90
+ 'name1.20100704.weekly',
91
+ 'name2.20100721.daily'
92
+ ].each do |file_part|
93
+ assert_no_match( /#{file_part}.tar.gz/, files_keeped )
94
+ assert_no_match( /#{file_part}.sql.tar.gz/, databases_keeped )
95
+ end
96
+ end
97
+
98
+
99
+ end
100
+
@@ -0,0 +1,59 @@
1
+ require "#{File.dirname(__FILE__)}/../test_helper"
2
+
3
+ class RunnerS3Test < Test::Unit::TestCase
4
+
5
+ def setup
6
+ SweetyBacky::Utils.stubs(:log)
7
+
8
+ # runner
9
+ @opts = {
10
+ :paths => [ "#{FIXTURES_PATH}/path" ],
11
+ :databases => [ "test" ],
12
+ :yearly => 1,
13
+ :monthly => 1,
14
+ :weekly => 2,
15
+ :daily => 4,
16
+ :database_user => 'test',
17
+ :database_pass => '',
18
+ :storage_system => :s3,
19
+ :s3_opts => {
20
+ :bucket => 'sweety_backy_test',
21
+ :passwd_file => '~/.s3.passwd',
22
+ :path => 'test/path'
23
+ }
24
+ }
25
+
26
+ @runner = SweetyBacky::Runner.new
27
+ @runner.config( @opts )
28
+
29
+ s3 = ::S3::Service.new( SweetyBacky::S3.read_s3_password( @opts[:s3_opts][:passwd_file] ) )
30
+ @bucket = s3.buckets.build( @opts[:s3_opts][:bucket] )
31
+ @bucket.save
32
+ end
33
+
34
+ def teardown
35
+ @bucket.destroy( true )
36
+ end
37
+
38
+ def test_do_backup_daily
39
+ SweetyBacky::Utils.stubs( :period ).returns( 'daily' )
40
+
41
+ @runner.do_backup
42
+
43
+ assert(
44
+ @bucket.
45
+ object(
46
+ "test/path/files/#{SweetyBacky::Utils.namerize( @opts[:paths][0] )}.#{Date.today.strftime('%Y%m%d')}.daily.tar.gz"
47
+ ).exists?
48
+ )
49
+
50
+ assert(
51
+ @bucket.
52
+ object(
53
+ "test/path/databases/test.#{Date.today.strftime('%Y%m%d')}.daily.sql.tar.gz"
54
+ ).exists?
55
+ )
56
+ end
57
+
58
+ end
59
+
@@ -0,0 +1,61 @@
1
+ require "#{File.dirname(__FILE__)}/../test_helper"
2
+
3
+
4
+ class S3Test < Test::Unit::TestCase
5
+ def setup
6
+ SweetyBacky::Utils.stubs(:log)
7
+
8
+ @opts = {
9
+ :bucket => 'sweety_backy_test',
10
+ :path => 'test_path',
11
+ :passwd_file => '~/.s3.passwd'
12
+ }
13
+
14
+ s3 = ::S3::Service.new( SweetyBacky::S3.read_s3_password( @opts[:passwd_file] ) )
15
+ @bucket = s3.buckets.build( @opts[:bucket] )
16
+ @bucket.save
17
+ end
18
+
19
+ def teardown
20
+ @bucket.destroy( true )
21
+ end
22
+
23
+ def test_upload
24
+ SweetyBacky::S3.upload(
25
+ "#{FIXTURES_PATH}/file.txt",
26
+ "test/path/file.txt",
27
+ @opts
28
+ )
29
+
30
+ assert_equal(
31
+ File.read( "#{FIXTURES_PATH}/file.txt" ),
32
+ SweetyBacky::S3.object( "test/path/file.txt", @opts ).content
33
+ )
34
+ end
35
+
36
+ def test_paths_in
37
+ SweetyBacky::S3.upload( "#{FIXTURES_PATH}/file.txt", "test/path/file1.txt", @opts )
38
+ SweetyBacky::S3.upload( "#{FIXTURES_PATH}/file.txt", "test/path/file2.txt", @opts )
39
+ SweetyBacky::S3.upload( "#{FIXTURES_PATH}/file.txt", "test/path/file3.txt", @opts )
40
+ SweetyBacky::S3.upload( "#{FIXTURES_PATH}/file.txt", "test/path/other_file.txt", @opts )
41
+
42
+ paths = SweetyBacky::S3.paths_in( "test/path/file*.txt", @opts )
43
+
44
+ assert_equal(3, paths.size)
45
+ assert( ( paths.include? "test/path/file1.txt" ) )
46
+ assert( ( paths.include? "test/path/file2.txt" ) )
47
+ assert( ( paths.include? "test/path/file3.txt" ) )
48
+ assert( ( !paths.include? "test/path/other_file.txt" ) )
49
+ end
50
+
51
+ def test_delete
52
+ SweetyBacky::S3.upload( "#{FIXTURES_PATH}/file.txt", "test/path/file1.txt", @opts )
53
+ SweetyBacky::S3.upload( "#{FIXTURES_PATH}/file.txt", "test/path/file2.txt", @opts )
54
+
55
+ SweetyBacky::S3.delete( "test/path/file2.txt", @opts )
56
+
57
+ assert( @bucket.object( "test/path/file1.txt" ).exists? )
58
+ assert( !@bucket.object( "test/path/file2.txt" ).exists? )
59
+ end
60
+
61
+ end
@@ -0,0 +1,9 @@
1
+ require "#{File.dirname(__FILE__)}/../lib/sweety_backy"
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'mocha'
6
+ require 'delorean'
7
+
8
+
9
+ FIXTURES_PATH = File.expand_path "#{File.dirname(__FILE__)}/fixtures"
@@ -0,0 +1,24 @@
1
+ require "#{File.dirname(__FILE__)}/test_helper"
2
+
3
+ class UtilsTest < Test::Unit::TestCase
4
+ def setup
5
+ SweetyBacky::Utils.stubs(:log)
6
+ end
7
+
8
+ def test_error_on_command
9
+ assert_raise RuntimeError do
10
+ SweetyBacky::Utils::command( 'command_not_exists' )
11
+ end
12
+ end
13
+
14
+ def test_namerize
15
+ assert_equal(
16
+ 'Users.fguillen.Develop.Ruby.SweetyBacky.test.fixtures.path',
17
+ SweetyBacky::Utils.namerize( '/Users/fguillen/Develop/Ruby/SweetyBacky/test/fixtures/path' )
18
+ )
19
+
20
+ assert_equal( 'path', SweetyBacky::Utils.namerize( '/path' ) )
21
+ assert_equal( 'path', SweetyBacky::Utils.namerize( 'path' ) )
22
+ end
23
+
24
+ end
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sweety_backy
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Fernando Guillen
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-05 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: bundler
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 15424057
30
+ segments:
31
+ - 1
32
+ - 0
33
+ - 0
34
+ - rc
35
+ - 6
36
+ version: 1.0.0.rc.6
37
+ type: :development
38
+ version_requirements: *id001
39
+ - !ruby/object:Gem::Dependency
40
+ name: mocha
41
+ prerelease: false
42
+ requirement: &id002 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ hash: 3
48
+ segments:
49
+ - 0
50
+ version: "0"
51
+ type: :development
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: delorean
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ type: :development
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: s3
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ type: :runtime
80
+ version_requirements: *id004
81
+ description: Simple mechanism to configure and execute backups of folders and MySQL DBs and store them in local folder or S3 bucket
82
+ email:
83
+ - fguillen.mail@gmail.com
84
+ executables:
85
+ - sweety_backy
86
+ extensions: []
87
+
88
+ extra_rdoc_files: []
89
+
90
+ files:
91
+ - .gitignore
92
+ - .rvmrc_example
93
+ - Gemfile
94
+ - README.md
95
+ - Rakefile
96
+ - bin/sweety_backy
97
+ - lib/sweety_backy.rb
98
+ - lib/sweety_backy/commander.rb
99
+ - lib/sweety_backy/opts_reader.rb
100
+ - lib/sweety_backy/runner.rb
101
+ - lib/sweety_backy/s3.rb
102
+ - lib/sweety_backy/utils.rb
103
+ - lib/sweety_backy/version.rb
104
+ - server1/backups/databases/test.20100104.daily.sql.tar.gz
105
+ - server1/backups/files/Users.fguillen.Develop.Ruby.SweetyBacky.test.fixtures.path.20100104.daily.tar.gz
106
+ - sweety_backy.gemspec
107
+ - test/commander_test.rb
108
+ - test/fixtures/config_s3.yml
109
+ - test/fixtures/file.txt
110
+ - test/fixtures/path/a/file2.txt
111
+ - test/fixtures/path/b/file3.txt
112
+ - test/fixtures/path/file1.txt
113
+ - test/fixtures/s3.passwd
114
+ - test/opts_reader_test.rb
115
+ - test/runner_test.rb
116
+ - test/s3/commander_s3_test.rb
117
+ - test/s3/runner_s3_test.rb
118
+ - test/s3/s3_test.rb
119
+ - test/test_helper.rb
120
+ - test/utils_test.rb
121
+ has_rdoc: true
122
+ homepage: ""
123
+ licenses: []
124
+
125
+ post_install_message:
126
+ rdoc_options: []
127
+
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ hash: 3
136
+ segments:
137
+ - 0
138
+ version: "0"
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ none: false
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ hash: 3
145
+ segments:
146
+ - 0
147
+ version: "0"
148
+ requirements: []
149
+
150
+ rubyforge_project: SweetyBacky
151
+ rubygems_version: 1.3.7
152
+ signing_key:
153
+ specification_version: 3
154
+ summary: Ruby backup mechanism
155
+ test_files:
156
+ - test/commander_test.rb
157
+ - test/fixtures/config_s3.yml
158
+ - test/fixtures/file.txt
159
+ - test/fixtures/path/a/file2.txt
160
+ - test/fixtures/path/b/file3.txt
161
+ - test/fixtures/path/file1.txt
162
+ - test/fixtures/s3.passwd
163
+ - test/opts_reader_test.rb
164
+ - test/runner_test.rb
165
+ - test/s3/commander_s3_test.rb
166
+ - test/s3/runner_s3_test.rb
167
+ - test/s3/s3_test.rb
168
+ - test/test_helper.rb
169
+ - test/utils_test.rb