xaviershay-db2s3 0.2
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/README +31 -0
- data/Rakefile +4 -0
- data/db2s3.gemspec +46 -0
- data/init.rb +1 -0
- data/lib/db2s3.rb +107 -0
- data/rails/init.rb +1 -0
- data/spec/db2s3_spec.rb +38 -0
- data/spec/mysql_drop_schema.sql +1 -0
- data/spec/mysql_schema.sql +4 -0
- data/spec/s3_config.example.rb +7 -0
- data/spec/spec_helper.rb +14 -0
- data/tasks/tasks.rake +29 -0
- metadata +78 -0
data/README
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
DB2S3 - A rails plugin to backup Mysql to Amazon S3
|
|
2
|
+
---------------------------------------------------
|
|
3
|
+
|
|
4
|
+
Dependencies:
|
|
5
|
+
gem install aws-s3
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
./script/plugin install git://github.com/xaviershay/db2s3.git
|
|
9
|
+
# In config/initializers/db2s3.rb
|
|
10
|
+
DB2S3::Config.instance_eval do
|
|
11
|
+
S3 = {
|
|
12
|
+
:access_key_id => 'yourkey',
|
|
13
|
+
:secret_access_key => 'yoursecretkey',
|
|
14
|
+
:bucket => 'yourapp-db-backup'
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
# DB credentials are read from your rails environment
|
|
18
|
+
|
|
19
|
+
# Add to your crontab or whatever
|
|
20
|
+
rake db2s3:backup:full
|
|
21
|
+
rake db2s3:backup:incremental # Unimplemented
|
|
22
|
+
|
|
23
|
+
# Handy tasks
|
|
24
|
+
rake db2s3:metrics # Estimated costs
|
|
25
|
+
rake db2s3:backup:restore # You should be testing this regularly
|
|
26
|
+
|
|
27
|
+
Caveats:
|
|
28
|
+
Currently only stores the latest backup
|
|
29
|
+
|
|
30
|
+
Kudos:
|
|
31
|
+
http://github.com/pauldowman/blog_code_examples/tree/master/mysql_s3_backup
|
data/Rakefile
ADDED
data/db2s3.gemspec
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.name = %q{db2s3}
|
|
5
|
+
s.version = "0.2"
|
|
6
|
+
|
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
8
|
+
s.authors = ["Xavier Shay"]
|
|
9
|
+
s.date = %q{2009-03-08}
|
|
10
|
+
s.description = %q{db2s3 provides rake tasks for backing up and restoring your DB to S3}
|
|
11
|
+
s.email = %q{contact@rhnh.net}
|
|
12
|
+
s.files = %w(
|
|
13
|
+
README
|
|
14
|
+
Rakefile
|
|
15
|
+
db2s3.gemspec
|
|
16
|
+
init.rb
|
|
17
|
+
lib
|
|
18
|
+
lib/db2s3.rb
|
|
19
|
+
rails
|
|
20
|
+
rails/init.rb
|
|
21
|
+
spec
|
|
22
|
+
spec/db2s3_spec.rb
|
|
23
|
+
spec/mysql_drop_schema.sql
|
|
24
|
+
spec/mysql_schema.sql
|
|
25
|
+
spec/s3_config.example.rb
|
|
26
|
+
spec/s3_config.rb
|
|
27
|
+
spec/spec_helper.rb
|
|
28
|
+
tasks
|
|
29
|
+
tasks/tasks.rake
|
|
30
|
+
)
|
|
31
|
+
s.has_rdoc = false
|
|
32
|
+
s.homepage = %q{http://github.com/xaviershay/db2s3}
|
|
33
|
+
#s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
|
|
34
|
+
s.require_paths = ["lib"]
|
|
35
|
+
#s.rubyforge_project = %q{grit}
|
|
36
|
+
s.rubygems_version = %q{1.3.0}
|
|
37
|
+
s.summary = %q{db2s3 provides rake tasks for backing up and restoring your DB to S3}
|
|
38
|
+
|
|
39
|
+
# TODO: WTF does this do
|
|
40
|
+
if s.respond_to? :specification_version then
|
|
41
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
42
|
+
s.specification_version = 2
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
s.add_dependency(%q<aws-s3>, [">= 0.5.1"])
|
|
46
|
+
end
|
data/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + "/rails/init"
|
data/lib/db2s3.rb
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'aws/s3'
|
|
2
|
+
|
|
3
|
+
class DB2S3
|
|
4
|
+
class Config
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def full_backup
|
|
11
|
+
store.store("dump-#{db_credentials[:database]}.sql.gz", open(dump_db.path))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def restore
|
|
15
|
+
file = store.fetch("dump-#{db_credentials[:database]}.sql.gz")
|
|
16
|
+
run "gunzip -c #{file.path} | mysql #{mysql_options}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def metrics
|
|
20
|
+
dump_file = dump_db
|
|
21
|
+
|
|
22
|
+
storage_dollars_per_byte_per_month = 0.15 / 1024.0 / 1024.0 / 1024.0
|
|
23
|
+
transfer_dollars_per_byte_per_month = 0.10 / 1024.0 / 1024.0 / 1024.0
|
|
24
|
+
full_dumps_per_month = 30
|
|
25
|
+
|
|
26
|
+
storage_cost = dump_file.size * storage_dollars_per_byte_per_month
|
|
27
|
+
transfer_cost = dump_file.size * full_dumps_per_month * transfer_dollars_per_byte_per_month
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
:db_size => dump_file.size,
|
|
31
|
+
:storage_cost => storage_cost,
|
|
32
|
+
:transfer_cost => transfer_cost,
|
|
33
|
+
:total_cost => storage_cost + transfer_cost,
|
|
34
|
+
:full_backups_per_month => full_dumps_per_month
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def dump_db
|
|
41
|
+
dump_file = Tempfile.new("dump")
|
|
42
|
+
|
|
43
|
+
#cmd = "mysqldump --quick --single-transaction --create-options -u#{db_credentials[:user]} --flush-logs --master-data=2 --delete-master-logs"
|
|
44
|
+
cmd = "mysqldump --quick --single-transaction --create-options #{mysql_options}"
|
|
45
|
+
cmd += " | gzip > #{dump_file.path}"
|
|
46
|
+
run(cmd)
|
|
47
|
+
|
|
48
|
+
dump_file
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def mysql_options
|
|
52
|
+
cmd = " -u#{db_credentials[:user]} "
|
|
53
|
+
cmd += " -p'#{db_credentials[:password]}'" unless db_credentials[:password].nil?
|
|
54
|
+
cmd += " #{db_credentials[:database]}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def store
|
|
58
|
+
@store ||= S3Store.new
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def run(command)
|
|
62
|
+
result = system(command)
|
|
63
|
+
raise("error, process exited with status #{$?.exitstatus}") unless result
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def db_credentials
|
|
67
|
+
ActiveRecord::Base.connection.instance_eval { @config } # Dodgy!
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class S3Store
|
|
71
|
+
def initialize
|
|
72
|
+
@connected = false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ensure_connected
|
|
76
|
+
return if @connected
|
|
77
|
+
AWS::S3::Base.establish_connection!(DB2S3::Config::S3.slice(:access_key_id, :secret_access_key).merge(:use_ssl => true))
|
|
78
|
+
AWS::S3::Bucket.create(bucket)
|
|
79
|
+
@connected = true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def store(file_name, file)
|
|
83
|
+
ensure_connected
|
|
84
|
+
AWS::S3::S3Object.store(file_name, file, bucket)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def fetch(file_name)
|
|
88
|
+
ensure_connected
|
|
89
|
+
AWS::S3::S3Object.find(file_name, bucket)
|
|
90
|
+
|
|
91
|
+
file = Tempfile.new("dump")
|
|
92
|
+
open(file.path, 'w') do |f|
|
|
93
|
+
AWS::S3::S3Object.stream(file_name, bucket) do |chunk|
|
|
94
|
+
f.write chunk
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
file
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def bucket
|
|
103
|
+
DB2S3::Config::S3[:bucket]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
data/rails/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/lib/db2s3')
|
data/spec/db2s3_spec.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'db2s3' do
|
|
4
|
+
def load_schema
|
|
5
|
+
`cat '#{File.dirname(__FILE__) + '/mysql_schema.sql'}' | mysql -u #{DBConfig[:user]} #{DBConfig[:database]}`
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def drop_schema
|
|
9
|
+
`cat '#{File.dirname(__FILE__) + '/mysql_drop_schema.sql'}' | mysql -u #{DBConfig[:user]} #{DBConfig[:database]}`
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class Person < ActiveRecord::Base
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'can save and restore a backup to S3' do
|
|
16
|
+
db2s3 = DB2S3.new
|
|
17
|
+
load_schema
|
|
18
|
+
Person.create!(:name => "Baxter")
|
|
19
|
+
db2s3.full_backup
|
|
20
|
+
drop_schema
|
|
21
|
+
db2s3.restore
|
|
22
|
+
Person.find_by_name("Baxter").should_not be_nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'provides estimated metrics' do
|
|
26
|
+
db2s3 = DB2S3.new
|
|
27
|
+
# 1 GB DB
|
|
28
|
+
db2s3.stub!(:dump_db).and_return(stub("dump file", :size => 1024 * 1024 * 1024))
|
|
29
|
+
metrics = db2s3.metrics
|
|
30
|
+
metrics.should == {
|
|
31
|
+
:storage_cost => 0.15, # 15c/GB-Month, we're only storing one backup
|
|
32
|
+
:transfer_cost => 3.0, # 10c/GB-Month * 30 backups
|
|
33
|
+
:db_size => 1024 * 1024 * 1024, # 1 GB
|
|
34
|
+
:total_cost => 3.15,
|
|
35
|
+
:full_backups_per_month => 30 # Default 1 backup/day
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DROP TABLE IF EXISTS people;
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'spec'
|
|
2
|
+
require 'activerecord'
|
|
3
|
+
require File.dirname(__FILE__) + '/../lib/db2s3'
|
|
4
|
+
require File.dirname(__FILE__) + '/s3_config.rb'
|
|
5
|
+
|
|
6
|
+
DBConfig = {
|
|
7
|
+
:adapter => "mysql",
|
|
8
|
+
:encoding => "utf8",
|
|
9
|
+
:database => 'db2s3_unittest',
|
|
10
|
+
:user => "root"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Base.configurations = { 'production' => DBConfig }
|
|
14
|
+
ActiveRecord::Base.establish_connection(:production)
|
data/tasks/tasks.rake
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
namespace :db2s3 do
|
|
2
|
+
namespace :backup do
|
|
3
|
+
desc "Save a full back to S3"
|
|
4
|
+
task :full => :environment do
|
|
5
|
+
DB2S3.new.full_backup
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
desc "Provide estimated costs for backing up your DB to S3"
|
|
10
|
+
task :metrics => :environment do
|
|
11
|
+
def format_size(size)
|
|
12
|
+
units = %w{B KB MB GB TB}
|
|
13
|
+
e = (Math.log(size)/Math.log(1024)).floor
|
|
14
|
+
s = "%.3f" % (size.to_f / 1024**e)
|
|
15
|
+
s.sub(/\.?0*$/, units[e])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
metrics = DB2S3.new.metrics
|
|
19
|
+
puts <<-EOS
|
|
20
|
+
Estimates only, does not take into account metadata overhead
|
|
21
|
+
|
|
22
|
+
DB Size: #{format_size(metrics[:db_size])}
|
|
23
|
+
Full backups/month: #{metrics[:full_backups_per_month]}
|
|
24
|
+
Storage Cost $US: #{metrics[:storage_cost]}
|
|
25
|
+
Transfer Cost $US: #{metrics[:transfer_cost]}
|
|
26
|
+
Total Cost $US: #{metrics[:total_cost]}
|
|
27
|
+
EOS
|
|
28
|
+
end
|
|
29
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: xaviershay-db2s3
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: "0.2"
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Xavier Shay
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-03-08 00:00:00 -08:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: aws-s3
|
|
17
|
+
type: :runtime
|
|
18
|
+
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: 0.5.1
|
|
24
|
+
version:
|
|
25
|
+
description: db2s3 provides rake tasks for backing up and restoring your DB to S3
|
|
26
|
+
email: contact@rhnh.net
|
|
27
|
+
executables: []
|
|
28
|
+
|
|
29
|
+
extensions: []
|
|
30
|
+
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
|
|
33
|
+
files:
|
|
34
|
+
- README
|
|
35
|
+
- Rakefile
|
|
36
|
+
- db2s3.gemspec
|
|
37
|
+
- init.rb
|
|
38
|
+
- lib
|
|
39
|
+
- lib/db2s3.rb
|
|
40
|
+
- rails
|
|
41
|
+
- rails/init.rb
|
|
42
|
+
- spec
|
|
43
|
+
- spec/db2s3_spec.rb
|
|
44
|
+
- spec/mysql_drop_schema.sql
|
|
45
|
+
- spec/mysql_schema.sql
|
|
46
|
+
- spec/s3_config.example.rb
|
|
47
|
+
- spec/s3_config.rb
|
|
48
|
+
- spec/spec_helper.rb
|
|
49
|
+
- tasks
|
|
50
|
+
- tasks/tasks.rake
|
|
51
|
+
has_rdoc: false
|
|
52
|
+
homepage: http://github.com/xaviershay/db2s3
|
|
53
|
+
post_install_message:
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
|
|
56
|
+
require_paths:
|
|
57
|
+
- lib
|
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: "0"
|
|
63
|
+
version:
|
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: "0"
|
|
69
|
+
version:
|
|
70
|
+
requirements: []
|
|
71
|
+
|
|
72
|
+
rubyforge_project:
|
|
73
|
+
rubygems_version: 1.2.0
|
|
74
|
+
signing_key:
|
|
75
|
+
specification_version: 2
|
|
76
|
+
summary: db2s3 provides rake tasks for backing up and restoring your DB to S3
|
|
77
|
+
test_files: []
|
|
78
|
+
|