jemmyw-backs3 0.0.3 → 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.
- data/Manifest.txt +3 -0
- data/README.txt +1 -1
- data/bin/res3 +7 -1
- data/lib/backs3/backs3.rb +30 -5
- data/lib/backs3/backup.rb +5 -70
- data/lib/backs3/restore.rb +29 -44
- data/lib/backs3/version.rb +1 -1
- data/spec/backs3/backup_spec.rb +0 -62
- data/spec/backs3/restore_spec.rb +47 -14
- data/spec/spec.opts +2 -1
- metadata +3 -3
data/Manifest.txt
CHANGED
@@ -10,8 +10,11 @@ lib/backs3/backs3.rb
|
|
10
10
|
lib/backs3/backup.rb
|
11
11
|
lib/backs3/restore.rb
|
12
12
|
lib/backs3/version.rb
|
13
|
+
lib/backs3/backup_info.rb
|
13
14
|
spec/spec.opts
|
14
15
|
spec/spec_helper.rb
|
15
16
|
spec/backs3/backup_spec.rb
|
16
17
|
spec/backs3/restore_spec.rb
|
18
|
+
spec/backs3/backup_info_spec.rb
|
19
|
+
spec/backs3/backup_file_info_spec.rb
|
17
20
|
tasks/rspec.rake
|
data/README.txt
CHANGED
@@ -29,7 +29,7 @@ A simple backup and restore program for S3
|
|
29
29
|
|
30
30
|
(The MIT License)
|
31
31
|
|
32
|
-
Copyright (c) 2009 Jeremy Wells / Boost Limited
|
32
|
+
Copyright (c) 2009 Jeremy Wells / Boost Limited (http://www.boost.co.nz)
|
33
33
|
|
34
34
|
Permission is hereby granted, free of charge, to any person obtaining
|
35
35
|
a copy of this software and associated documentation files (the
|
data/bin/res3
CHANGED
data/lib/backs3/backs3.rb
CHANGED
@@ -5,11 +5,17 @@ require 'aws/s3'
|
|
5
5
|
require 'active_support'
|
6
6
|
require 'digest/md5'
|
7
7
|
require 'time'
|
8
|
+
require File.join(File.dirname(__FILE__), 'backup_info')
|
8
9
|
|
9
10
|
$has_md5 = !(`which md5`).blank?
|
10
11
|
|
11
12
|
module Backs3
|
12
13
|
include AWS::S3
|
14
|
+
|
15
|
+
def logger
|
16
|
+
logger_output = @options['logger'] || $stdout
|
17
|
+
@logger ||= Logger.new(logger_output)
|
18
|
+
end
|
13
19
|
|
14
20
|
def establish_connection
|
15
21
|
AWS::S3::Base.establish_connection!(
|
@@ -20,20 +26,39 @@ module Backs3
|
|
20
26
|
|
21
27
|
def md5(filename)
|
22
28
|
if $has_md5
|
23
|
-
`md5 #{filename}`
|
29
|
+
`md5 -q #{filename}`
|
24
30
|
else
|
25
31
|
Digest::MD5.hexdigest(filename)
|
26
32
|
end
|
27
33
|
end
|
34
|
+
|
35
|
+
def save_backup_info(info)
|
36
|
+
S3Object.store(@options['prefix'] + 's3backup', YAML.dump(info), @options['bucket'])
|
37
|
+
logger.info "Backup info has been stored"
|
38
|
+
end
|
28
39
|
|
29
|
-
def
|
30
|
-
@
|
40
|
+
def load_backup_info
|
41
|
+
@backups ||= begin
|
31
42
|
backup_info_file = S3Object.find(@options['prefix'] + 's3backup', @options['bucket'])
|
32
43
|
backup_info_data = backup_info_file.value(:reload)
|
33
|
-
YAML.load(backup_info_data) || {}
|
44
|
+
YAML.load(backup_info_data) || {}
|
34
45
|
rescue Exception => e
|
35
46
|
puts e.to_s
|
36
|
-
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
unless @backups.respond_to?(:sort) && @backups.respond_to?(:each) && @backups.respond_to?(:reject!)
|
51
|
+
@backups = []
|
52
|
+
end
|
53
|
+
|
54
|
+
@backups.reject! do |backup|
|
55
|
+
!backup.respond_to?(:date)
|
56
|
+
end
|
57
|
+
|
58
|
+
@backups.sort do |a,b|
|
59
|
+
a.date <=> b.date
|
37
60
|
end
|
61
|
+
|
62
|
+
@backups
|
38
63
|
end
|
39
64
|
end
|
data/lib/backs3/backup.rb
CHANGED
@@ -3,84 +3,19 @@ require File.dirname(__FILE__) + '/backs3'
|
|
3
3
|
module Backs3
|
4
4
|
class Backup
|
5
5
|
include Backs3
|
6
|
-
include AWS::S3
|
7
6
|
|
8
7
|
def initialize(options = {})
|
9
8
|
@options = options
|
10
9
|
@options['prefix'] ||= ''
|
11
|
-
end
|
12
|
-
|
13
|
-
def first_backup?
|
14
|
-
last_backup.nil?
|
15
|
-
end
|
16
|
-
|
17
|
-
def full_backup
|
18
|
-
@options['force-full'] || first_backup? || Time.now.to_i - last_backup > (@options['full'] || 7).days
|
19
|
-
end
|
20
10
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
def backup_key
|
26
|
-
@options['prefix'] + @current_backup
|
27
|
-
end
|
28
|
-
|
29
|
-
def last_key
|
30
|
-
@options['prefix'] + last_backup.to_s
|
31
|
-
end
|
32
|
-
|
33
|
-
def files_to_backup
|
34
|
-
@files_to_backup ||= begin
|
35
|
-
Dir.glob(File.join(@options['folder'], '**', '**')).select do |file|
|
36
|
-
if File.directory?(file) || File.symlink?(file)
|
37
|
-
false
|
38
|
-
else
|
39
|
-
if @options['exclude'].blank? || file !~ /#{@options['exclude']}/
|
40
|
-
if full_backup || File.mtime(file).to_i > last_backup
|
41
|
-
true
|
42
|
-
else
|
43
|
-
false
|
44
|
-
end
|
45
|
-
else
|
46
|
-
false
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
11
|
+
establish_connection
|
12
|
+
|
13
|
+
@backups = load_backup_info
|
14
|
+
@backup = BackupInfo.new(@backups, @options)
|
51
15
|
end
|
52
16
|
|
53
17
|
def backup
|
54
|
-
@
|
55
|
-
@files_to_backup = nil
|
56
|
-
@backup_info = nil
|
57
|
-
|
58
|
-
establish_connection
|
59
|
-
|
60
|
-
puts "Backup started at #{Time.now}"
|
61
|
-
puts "Last backup happened at #{backup_info['last_backup']}"
|
62
|
-
puts "Performing %s" % (full_backup ? "full backup" : "incremental backup")
|
63
|
-
|
64
|
-
files_to_backup.each do |filename|
|
65
|
-
puts "Backing up #{filename}"
|
66
|
-
file_md5 = md5(filename)
|
67
|
-
aws_filename = File.join(backup_key, filename)
|
68
|
-
|
69
|
-
object = S3Object.find(aws_filename, @options['bucket']) rescue nil
|
70
|
-
|
71
|
-
if object.nil? || object.metadata[:md5sum] != file_md5
|
72
|
-
S3Object.store(aws_filename, open(filename), @options['bucket'])
|
73
|
-
object = S3Object.find(aws_filename, @options['bucket'])
|
74
|
-
object.metadata[:md5sum] = file_md5
|
75
|
-
object.save
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
backup_info['last_backup'] = @current_backup
|
80
|
-
backup_info['backups'] ||= []
|
81
|
-
backup_info['backups'] << @current_backup
|
82
|
-
S3Object.store(@options['prefix'] + 's3backup', YAML.dump(backup_info), @options['bucket'])
|
83
|
-
puts "Backup completed, #{files_to_backup.size} files backed up"
|
18
|
+
@backup.backup
|
84
19
|
end
|
85
20
|
end
|
86
21
|
end
|
data/lib/backs3/restore.rb
CHANGED
@@ -7,39 +7,44 @@ module Backs3
|
|
7
7
|
include AWS::S3
|
8
8
|
|
9
9
|
def self.commands
|
10
|
-
%w(ls available restore cat)
|
10
|
+
%w(ls available restore cat info)
|
11
11
|
end
|
12
12
|
|
13
13
|
def initialize(options = {})
|
14
14
|
@options = options
|
15
15
|
@options['prefix'] ||= ''
|
16
16
|
establish_connection
|
17
|
+
@backups = load_backup_info.sort{|a,b| a.date <=> b.date }
|
17
18
|
end
|
18
19
|
|
19
|
-
def available(
|
20
|
-
if
|
21
|
-
puts "Backups available: #{
|
20
|
+
def available(backup_key = nil)
|
21
|
+
if backup_key.nil?
|
22
|
+
puts "Backups available: #{@backups.map{|b| b.date}.join(", ")}"
|
22
23
|
else
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
if object
|
28
|
-
{
|
29
|
-
:backup => backup,
|
30
|
-
:md5 => object.metadata[:md5sum]
|
31
|
-
}
|
32
|
-
else
|
33
|
-
nil
|
34
|
-
end
|
24
|
+
unless backup = @backups.detect{|b| b.date.to_s == backup_key.to_s }
|
25
|
+
raise "No backup #{backup_key} available"
|
26
|
+
end
|
35
27
|
|
36
|
-
|
28
|
+
files = backup.all_files
|
37
29
|
|
38
|
-
puts "Backup information for #{
|
39
|
-
|
40
|
-
puts "\
|
30
|
+
puts "Backup information for #{backup.date}"
|
31
|
+
files.each do |file|
|
32
|
+
puts "\tFile: #{file.path}, backed up #{Time.at(file.backup_info.date).to_s}"
|
41
33
|
end
|
34
|
+
end
|
35
|
+
end
|
42
36
|
|
37
|
+
def info(file)
|
38
|
+
files = @backups.collect{|b| b.files}.flatten.select{|f| f.path == file}
|
39
|
+
|
40
|
+
if files.empty?
|
41
|
+
puts "No information found for file #{file}"
|
42
|
+
else
|
43
|
+
puts "Backup information for file #{file}"
|
44
|
+
|
45
|
+
files.each do |f|
|
46
|
+
puts "\tBacked up #{Time.at(f.backup_info.date).to_s} in #{f.backup_info.date} with md5sum #{f.md5sum}"
|
47
|
+
end
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
@@ -60,30 +65,10 @@ module Backs3
|
|
60
65
|
end
|
61
66
|
end
|
62
67
|
|
63
|
-
def restore(
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
if file.nil?
|
68
|
-
objects = Bucket.objects(@options['bucket'], :prefix => @options['prefix'] + backup.to_s)
|
69
|
-
else
|
70
|
-
objects << S3Object.find(File.join(backup_key, file), @options['bucket']) rescue nil
|
71
|
-
end
|
72
|
-
|
73
|
-
objects.compact!
|
74
|
-
|
75
|
-
objects.each do |object|
|
76
|
-
$stdout.write "Restoring file #{object.key} to /tmp/#{object.key}"
|
77
|
-
filename = "/tmp/#{object.key}"
|
78
|
-
FileUtils.mkdir_p File.dirname(filename)
|
79
|
-
File.open(filename, 'w') do |f|
|
80
|
-
object.value do |segment|
|
81
|
-
$stdout.write "."
|
82
|
-
f.write segment
|
83
|
-
end
|
84
|
-
end
|
85
|
-
$stdout.write "\n"
|
86
|
-
end
|
68
|
+
def restore(date, file = nil)
|
69
|
+
backup = @backups.detect{|b| b.date.to_s == date.to_s}
|
70
|
+
raise 'Cannot find backup %s' % date if backup.nil?
|
71
|
+
backup.restore('/tmp', file)
|
87
72
|
end
|
88
73
|
end
|
89
74
|
end
|
data/lib/backs3/version.rb
CHANGED
data/spec/backs3/backup_spec.rb
CHANGED
@@ -8,66 +8,4 @@ describe Backup do
|
|
8
8
|
@bucket = 'test_bucket'
|
9
9
|
@backup = Backup.new({'bucket' => @bucket})
|
10
10
|
end
|
11
|
-
|
12
|
-
describe 'backup' do
|
13
|
-
it 'should backup all the files returned by files_to_backup'
|
14
|
-
end
|
15
|
-
|
16
|
-
describe 'first_backup?' do
|
17
|
-
it 'should be true if there is no previous backup' do
|
18
|
-
@backup.should_receive(:last_backup).and_return(nil)
|
19
|
-
@backup.first_backup?.should be_true
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'should be false if there is a previous backup' do
|
23
|
-
@backup.should_receive(:last_backup).and_return(12345)
|
24
|
-
@backup.first_backup?.should be_false
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
describe 'full_backup' do
|
29
|
-
it 'should be true if force-full option is set' do
|
30
|
-
@backup = Backup.new('force-full' => true)
|
31
|
-
@backup.should_not_receive(:last_backup)
|
32
|
-
@backup.should_not_receive(:first_backup?)
|
33
|
-
@backup.full_backup.should be_true
|
34
|
-
end
|
35
|
-
it 'should be true if this is the first backup' do
|
36
|
-
@backup.should_receive(:first_backup?).and_return(true)
|
37
|
-
@backup.full_backup.should be_true
|
38
|
-
end
|
39
|
-
it 'should be true if the last backup was more than 7 days ago' do
|
40
|
-
@backup.should_receive(:first_backup?).and_return(false)
|
41
|
-
@backup.should_receive(:last_backup).and_return((Time.now - 8.days).to_i)
|
42
|
-
@backup.full_backup.should be_true
|
43
|
-
end
|
44
|
-
it 'should be false' do
|
45
|
-
@backup.should_receive(:first_backup?).and_return(false)
|
46
|
-
@backup.should_receive(:last_backup).and_return(Time.now.to_i)
|
47
|
-
@backup.full_backup.should be_false
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
describe 'last_backup' do
|
52
|
-
it 'should return the integer time of the last backup' do
|
53
|
-
time = Time.now
|
54
|
-
time.should_receive(:to_i).and_return(12345)
|
55
|
-
@backup.should_receive(:backup_info).and_return({'last_backup' => time})
|
56
|
-
@backup.last_backup.should == 12345
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
describe 'backup_key' do
|
61
|
-
it 'should return the prefix + the backup time'
|
62
|
-
end
|
63
|
-
|
64
|
-
describe 'last_key' do
|
65
|
-
it 'should return the prefix + the last backup time'
|
66
|
-
end
|
67
|
-
|
68
|
-
describe 'files_to_backup' do
|
69
|
-
it 'should return all the files in the backup folder'
|
70
|
-
it 'should not return excluded files'
|
71
|
-
it 'should not return files that have not changed if this is an incremental backup'
|
72
|
-
end
|
73
11
|
end
|
data/spec/backs3/restore_spec.rb
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
require File.dirname(__FILE__) + "/../spec_helper"
|
2
2
|
require 'backs3/restore'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
describe Restore do
|
4
|
+
describe Backs3::Restore do
|
7
5
|
before(:each) do
|
8
6
|
AWS::S3::Base.stub!(:establish_connection!)
|
9
7
|
@bucket = 'test_bucket'
|
10
8
|
@restore = Restore.new('bucket' => @bucket)
|
11
9
|
|
12
10
|
@file_1 = mock(:s3object, :metadata => {})
|
11
|
+
|
12
|
+
@backup_mock1 = mock(:backup_info, :date => 12345, :full => true)
|
13
|
+
@backup_mock2 = mock(:backup_info, :date => 54321, :last_full_backup => @backup_mock1, :full => false)
|
14
|
+
|
15
|
+
@file_mock1 = mock(:file, :backup_info => @backup_mock1, :path => 'test/file_1')
|
16
|
+
@file_mock2 = mock(:file, :backup_info => @backup_mock1, :path => 'test/file_2')
|
17
|
+
@file_mock3 = mock(:file, :backup_info => @backup_mock1, :path => 'test/file_3')
|
18
|
+
@file_mock4 = mock(:file, :backup_info => @backup_mock2, :path => 'test/file_1')
|
19
|
+
|
20
|
+
@files_mock1 = [
|
21
|
+
@file_mock1, @file_mock2, @file_mock3
|
22
|
+
]
|
23
|
+
|
24
|
+
@files_mock2 = [
|
25
|
+
@file_mock4
|
26
|
+
]
|
27
|
+
|
28
|
+
@files_mock3 = [
|
29
|
+
@file_mock4, @file_mock2, @file_mock3
|
30
|
+
]
|
31
|
+
|
32
|
+
@backup_mock1.stub!(:files).and_return(@files_mock1)
|
33
|
+
@backup_mock2.stub!(:files).and_return(@files_mock2)
|
34
|
+
|
35
|
+
@backup_mock1.stub!(:all_files).and_return(@files_mock1)
|
36
|
+
@backup_mock2.stub!(:all_files).and_return(@files_mock3)
|
37
|
+
|
38
|
+
@backup_array = [@backup_mock1, @backup_mock2]
|
39
|
+
@restore.stub!(:load_backup_info).and_return(@backup_array)
|
40
|
+
@restore.instance_variable_set('@backups', @backup_array)
|
13
41
|
end
|
14
42
|
|
15
43
|
describe 'self.commands' do
|
@@ -19,22 +47,27 @@ describe Restore do
|
|
19
47
|
end
|
20
48
|
|
21
49
|
describe 'available' do
|
22
|
-
it 'should list all of the backups available
|
23
|
-
@restore.should_receive(:backup_info).and_return({'backups' => [12345, 54321]})
|
50
|
+
it 'should list all of the backups available' do
|
24
51
|
@restore.should_receive(:puts).with('Backups available: 12345, 54321')
|
25
52
|
@restore.available
|
26
53
|
end
|
27
|
-
it 'should list all of the backups a file is in' do
|
28
|
-
file = 'test/file_1'
|
29
|
-
@restore.should_receive(:backup_info).and_return({'backups' => [12345, 54321]})
|
30
|
-
S3Object.should_receive(:find).with('12345/test/file_1', @bucket).and_return(@file_1)
|
31
|
-
S3Object.should_receive(:find).with('54321/test/file_1', @bucket).and_return(nil)
|
32
54
|
|
33
|
-
|
34
|
-
@restore.should_receive(:puts).with(
|
35
|
-
@restore.
|
55
|
+
it 'should list all of the available files in a full backup' do
|
56
|
+
@restore.should_receive(:puts).once.with('Backup information for 12345')
|
57
|
+
@restore.should_receive(:puts).once.with("\tFile: test/file_1, backed up #{Time.at(12345).to_s}")
|
58
|
+
@restore.should_receive(:puts).once.with("\tFile: test/file_2, backed up #{Time.at(12345).to_s}")
|
59
|
+
@restore.should_receive(:puts).once.with("\tFile: test/file_3, backed up #{Time.at(12345).to_s}")
|
60
|
+
|
61
|
+
@restore.available(12345)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should list all the files from the last full backup for a partial backup' do
|
65
|
+
@restore.should_receive(:puts).once.with('Backup information for 54321')
|
66
|
+
@restore.should_receive(:puts).once.with("\tFile: test/file_1, backed up #{Time.at(54321).to_s}")
|
67
|
+
@restore.should_receive(:puts).once.with("\tFile: test/file_2, backed up #{Time.at(12345).to_s}")
|
68
|
+
@restore.should_receive(:puts).once.with("\tFile: test/file_3, backed up #{Time.at(12345).to_s}")
|
36
69
|
|
37
|
-
@restore.available(
|
70
|
+
@restore.available(54321)
|
38
71
|
end
|
39
72
|
end
|
40
73
|
|
data/spec/spec.opts
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
--colour
|
1
|
+
--colour
|
2
|
+
--reverse
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jemmyw-backs3
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Wells
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-04-27 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -97,7 +97,7 @@ requirements: []
|
|
97
97
|
rubyforge_project: backs3
|
98
98
|
rubygems_version: 1.2.0
|
99
99
|
signing_key:
|
100
|
-
specification_version:
|
100
|
+
specification_version: 3
|
101
101
|
summary: S3 backup and restore program
|
102
102
|
test_files: []
|
103
103
|
|