db2fog 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/HISTORY +17 -0
- data/README.rdoc +87 -0
- data/lib/db2fog.rb +162 -0
- data/lib/db2fog/railtie.rb +11 -0
- data/lib/db2fog/tasks.rb +28 -0
- metadata +158 -0
data/HISTORY
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
v0.4.0 (22nd July 2011)
|
2
|
+
- forked db2s3 into db2fog
|
3
|
+
0.3.1 (7 Dec 2009)
|
4
|
+
- Fixed hardcoded DB name in statistics task
|
5
|
+
0.3.0 (6 Dec 2009)
|
6
|
+
- Added db2s3:backup:clean task to delete old backups
|
7
|
+
- Added dependency on activesupport for the clean task
|
8
|
+
0.2.6 (6 Dec 2009)
|
9
|
+
- Remove metrics task, since it was far too inaccurate
|
10
|
+
- Add statistics task to show you the size of your tables
|
11
|
+
- Only add username to mysql command line if provided in database.yml
|
12
|
+
0.2.5
|
13
|
+
- Use host provided in database.yml
|
14
|
+
0.2.4 (2 Sep 2009)
|
15
|
+
- Fix credentials bug
|
16
|
+
0.2.3
|
17
|
+
- Keep old backups around
|
data/README.rdoc
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
= DB2Fog
|
2
|
+
|
3
|
+
A rails plugin to backup Mysql to a cloud storage provider. You're looking at
|
4
|
+
a monthly spend of four cents. So pony up you cheap bastard, and store your
|
5
|
+
backups offsite.
|
6
|
+
|
7
|
+
A grandfather style system to decide what backups to keep copies of:
|
8
|
+
|
9
|
+
* all backups from the past 24 hours
|
10
|
+
* one backup per day for the past week
|
11
|
+
* one backup per week forever
|
12
|
+
|
13
|
+
Depending on your tolerance for data loss you should be running a backup at
|
14
|
+
least once a day, probably more.
|
15
|
+
|
16
|
+
== Installation
|
17
|
+
|
18
|
+
Add the following to your project Gemfile
|
19
|
+
|
20
|
+
gem "db2fog"
|
21
|
+
|
22
|
+
== Configuration
|
23
|
+
|
24
|
+
Add the following to config/initializers/db2fog.rb
|
25
|
+
|
26
|
+
In general, you can use any configuration options supported by
|
27
|
+
Fog::Storage.new, plus the :directory option. If fog adds support for extra
|
28
|
+
providers they should work with just a config change to Db2Fog.
|
29
|
+
|
30
|
+
=== Amazon S3
|
31
|
+
|
32
|
+
DB2Fog.config = {
|
33
|
+
:aws_access_key_id => 'yourkey',
|
34
|
+
:aws_secret_access_key => 'yoursecretkey',
|
35
|
+
:directory => 'bucket-name',
|
36
|
+
:provider => 'AWS'
|
37
|
+
}
|
38
|
+
|
39
|
+
=== Rackspace Cloudfiles
|
40
|
+
|
41
|
+
DB2Fog.config = {
|
42
|
+
:rackspace_username => 'username',
|
43
|
+
:rackspace_api_key => 'api key',
|
44
|
+
:directory => 'bucket-name',
|
45
|
+
:provider => 'Rackspace'
|
46
|
+
}
|
47
|
+
|
48
|
+
=== Local Storage
|
49
|
+
|
50
|
+
DB2Fog.config = {
|
51
|
+
:directory => 'bucket-name',
|
52
|
+
:local_root => Rails.root.to_s + '/db/backups',
|
53
|
+
:provider => 'Local'
|
54
|
+
}
|
55
|
+
|
56
|
+
== Usage
|
57
|
+
|
58
|
+
# Add to your crontab or whatever
|
59
|
+
rake db2fog:backup:full
|
60
|
+
|
61
|
+
# Handy tasks
|
62
|
+
rake db2fog:statistics # Shows you the size of your DB
|
63
|
+
rake db2fog:backup:restore # You should be testing this regularly
|
64
|
+
rake db2fog:backup:clean # Clean up old backups - cron this
|
65
|
+
|
66
|
+
== Compatibility
|
67
|
+
|
68
|
+
This is pure so ruby should run on most ruby VMs. I develop on MRI 1.9.2.
|
69
|
+
|
70
|
+
This will only work work with rails 3. Supporting earlier versions is more
|
71
|
+
complication than I can handle at the moment.
|
72
|
+
|
73
|
+
== Development
|
74
|
+
|
75
|
+
Specs are really weak. This code is bit hackish but is being used by quite a few people.
|
76
|
+
|
77
|
+
== Kudos
|
78
|
+
|
79
|
+
This is a fork of Xavier Shay's db2s3 gem. It worked perfectly, but only
|
80
|
+
supported Amazon S3. By switching the dependency to using fog this now
|
81
|
+
supports multiple storage providers.
|
82
|
+
|
83
|
+
Xavier's original gem is available at https://github.com/xaviershay/db2s3
|
84
|
+
|
85
|
+
Xavier quotes the following example as inspiration:
|
86
|
+
|
87
|
+
http://github.com/pauldowman/blog_code_examples/tree/master/mysql_s3_backup
|
data/lib/db2fog.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
3
|
+
require 'active_support/core_ext/hash/except'
|
4
|
+
require 'fog'
|
5
|
+
require 'tempfile'
|
6
|
+
require 'db2fog/railtie'
|
7
|
+
|
8
|
+
class DB2Fog
|
9
|
+
cattr_accessor :config
|
10
|
+
|
11
|
+
def full_backup
|
12
|
+
file_name = "dump-#{db_credentials[:database]}-#{Time.now.utc.strftime("%Y%m%d%H%M")}.sql.gz"
|
13
|
+
store.store(file_name, open(dump_db.path))
|
14
|
+
store.store(most_recent_dump_file_name, file_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def restore
|
18
|
+
dump_file_name = store.fetch(most_recent_dump_file_name).read
|
19
|
+
file = store.fetch(dump_file_name)
|
20
|
+
run "gunzip -c #{file.path} | mysql #{mysql_options}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# TODO: This method really needs specs
|
24
|
+
def clean
|
25
|
+
to_keep = []
|
26
|
+
filelist = store.list
|
27
|
+
files = filelist.reject {|file| file.ends_with?(most_recent_dump_file_name) }.collect do |file|
|
28
|
+
{
|
29
|
+
:path => file,
|
30
|
+
:date => Time.parse(file.split('-').last.split('.').first)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
# Keep all backups from the past day
|
34
|
+
files.select {|x| x[:date] >= 1.day.ago }.each do |backup_for_day|
|
35
|
+
to_keep << backup_for_day
|
36
|
+
end
|
37
|
+
|
38
|
+
# Keep one backup per day from the last week
|
39
|
+
files.select {|x| x[:date] >= 1.week.ago }.group_by {|x| x[:date].strftime("%Y%m%d") }.values.each do |backups_for_last_week|
|
40
|
+
to_keep << backups_for_last_week.sort_by{|x| x[:date].strftime("%Y%m%d") }.first
|
41
|
+
end
|
42
|
+
|
43
|
+
# Keep one backup per week since forever
|
44
|
+
files.group_by {|x| x[:date].strftime("%Y%W") }.values.each do |backups_for_week|
|
45
|
+
to_keep << backups_for_week.sort_by{|x| x[:date].strftime("%Y%m%d") }.first
|
46
|
+
end
|
47
|
+
|
48
|
+
to_destroy = filelist - to_keep.uniq.collect {|x| x[:path] }
|
49
|
+
to_destroy.delete_if {|x| x.ends_with?(most_recent_dump_file_name) }
|
50
|
+
to_destroy.each do |file|
|
51
|
+
store.delete(file.split('/').last)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def statistics
|
56
|
+
# From http://mysqlpreacher.com/wordpress/tag/table-size/
|
57
|
+
results = ActiveRecord::Base.connection.execute(<<-EOS)
|
58
|
+
SELECT
|
59
|
+
engine,
|
60
|
+
ROUND(data_length/1024/1024,2) total_size_mb,
|
61
|
+
ROUND(index_length/1024/1024,2) total_index_size_mb,
|
62
|
+
table_rows,
|
63
|
+
table_name article_attachment
|
64
|
+
FROM information_schema.tables
|
65
|
+
WHERE table_schema = '#{db_credentials[:database]}'
|
66
|
+
ORDER BY total_size_mb + total_index_size_mb desc;
|
67
|
+
EOS
|
68
|
+
rows = []
|
69
|
+
results.each {|x| rows << x.to_a }
|
70
|
+
rows
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def dump_db
|
76
|
+
dump_file = Tempfile.new("dump")
|
77
|
+
|
78
|
+
cmd = "mysqldump --quick --single-transaction --create-options #{mysql_options}"
|
79
|
+
cmd += " | gzip > #{dump_file.path}"
|
80
|
+
run(cmd)
|
81
|
+
|
82
|
+
dump_file
|
83
|
+
end
|
84
|
+
|
85
|
+
def mysql_options
|
86
|
+
cmd = ''
|
87
|
+
cmd += " -u #{db_credentials[:username]} " unless db_credentials[:username].nil?
|
88
|
+
cmd += " -p'#{db_credentials[:password]}'" unless db_credentials[:password].nil?
|
89
|
+
cmd += " -h '#{db_credentials[:host]}'" unless db_credentials[:host].nil?
|
90
|
+
cmd += " #{db_credentials[:database]}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def store
|
94
|
+
@store ||= FogStore.new
|
95
|
+
end
|
96
|
+
|
97
|
+
def most_recent_dump_file_name
|
98
|
+
"most-recent-dump-#{db_credentials[:database]}.txt"
|
99
|
+
end
|
100
|
+
|
101
|
+
def run(command)
|
102
|
+
result = system(command)
|
103
|
+
raise("error, process exited with status #{$?.exitstatus}") unless result
|
104
|
+
end
|
105
|
+
|
106
|
+
def db_credentials
|
107
|
+
ActiveRecord::Base.connection.instance_eval { @config } # Dodgy!
|
108
|
+
end
|
109
|
+
|
110
|
+
class FogStore
|
111
|
+
|
112
|
+
def store(remote_filename, io)
|
113
|
+
unless directory.files.head(remote_filename)
|
114
|
+
directory.files.create(:key => remote_filename, :body => io, :public => false)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def fetch(remote_filename)
|
119
|
+
remote_file = directory.files.get(remote_filename)
|
120
|
+
|
121
|
+
file = Tempfile.new("dump")
|
122
|
+
open(file.path, 'w') { |f| f.write(remote_file.body) }
|
123
|
+
file
|
124
|
+
end
|
125
|
+
|
126
|
+
def list
|
127
|
+
directory.files.map { |f| f.key }
|
128
|
+
end
|
129
|
+
|
130
|
+
def delete(remote_filename)
|
131
|
+
remote_file = remote_file.head(remote_filename)
|
132
|
+
remote_file.destroy if remote_file
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def fog_options
|
138
|
+
if DB2Fog.config.respond_to?(:[])
|
139
|
+
DB2Fog.config.except(:directory)
|
140
|
+
else
|
141
|
+
raise "DB2Fog not configured"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def directory_name
|
146
|
+
if DB2Fog.config.respond_to?(:[])
|
147
|
+
DB2Fog.config[:directory]
|
148
|
+
else
|
149
|
+
raise "DB2Fog not configured"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def directory
|
154
|
+
@directory ||= storage.directories.get(directory_name)
|
155
|
+
end
|
156
|
+
|
157
|
+
def storage
|
158
|
+
@storage = Fog::Storage.new(fog_options)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
data/lib/db2fog/tasks.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
namespace :db2fog do
|
4
|
+
namespace :backup do
|
5
|
+
desc "Save a full back to S3"
|
6
|
+
task :full => :environment do
|
7
|
+
DB2Fog.new.full_backup
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Restore your DB from S3"
|
11
|
+
task :restore => :environment do
|
12
|
+
DB2Fog.new.restore
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Keep all backups for the last day, one per day for the last week, and one per week before that. Delete the rest."
|
16
|
+
task :clean => :environment do
|
17
|
+
DB2Fog.new.clean
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Show table sizes for your database"
|
22
|
+
task :statistics => :environment do
|
23
|
+
rows = DB2Fog.new.statistics
|
24
|
+
rows.sort_by {|x| -x[3].to_i }
|
25
|
+
header = [["Type", "Data MB", "Index", "Rows", "Name"], []]
|
26
|
+
puts (header + rows).collect {|x| x.join("\t") }
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: db2fog
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 4
|
8
|
+
version: "0.4"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- James Healy
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2011-07-22 00:00:00 +10:00
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: rails
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 3
|
29
|
+
- 0
|
30
|
+
version: "3.0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activerecord
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 3
|
43
|
+
- 0
|
44
|
+
version: "3.0"
|
45
|
+
type: :runtime
|
46
|
+
version_requirements: *id002
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: mysql2
|
49
|
+
prerelease: false
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
- 2
|
58
|
+
- 0
|
59
|
+
version: 0.2.0
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: fog
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ~>
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
- 9
|
73
|
+
- 0
|
74
|
+
version: 0.9.0
|
75
|
+
type: :runtime
|
76
|
+
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: rake
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
type: :development
|
89
|
+
version_requirements: *id005
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: rspec
|
92
|
+
prerelease: false
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ~>
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
segments:
|
99
|
+
- 2
|
100
|
+
- 6
|
101
|
+
version: "2.6"
|
102
|
+
type: :development
|
103
|
+
version_requirements: *id006
|
104
|
+
description: db2fig provides rake tasks for backing up and restoring your DB to cloud storage providers
|
105
|
+
email:
|
106
|
+
- james@yob.id.au
|
107
|
+
executables: []
|
108
|
+
|
109
|
+
extensions: []
|
110
|
+
|
111
|
+
extra_rdoc_files: []
|
112
|
+
|
113
|
+
files:
|
114
|
+
- lib/db2fog.rb
|
115
|
+
- lib/db2fog/railtie.rb
|
116
|
+
- lib/db2fog/tasks.rb
|
117
|
+
- README.rdoc
|
118
|
+
- HISTORY
|
119
|
+
has_rdoc: true
|
120
|
+
homepage: http://github.com/yob/db2fog
|
121
|
+
licenses: []
|
122
|
+
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options:
|
125
|
+
- --title
|
126
|
+
- DB2Fog
|
127
|
+
- --line-numbers
|
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
|
+
segments:
|
136
|
+
- 1
|
137
|
+
- 8
|
138
|
+
- 7
|
139
|
+
version: 1.8.7
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
none: false
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
segments:
|
146
|
+
- 1
|
147
|
+
- 3
|
148
|
+
- 2
|
149
|
+
version: 1.3.2
|
150
|
+
requirements: []
|
151
|
+
|
152
|
+
rubyforge_project:
|
153
|
+
rubygems_version: 1.3.7
|
154
|
+
signing_key:
|
155
|
+
specification_version: 3
|
156
|
+
summary: db2fog provides rake tasks for backing up and restoring your DB to cloud storage providers
|
157
|
+
test_files: []
|
158
|
+
|