ray-db2fog 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY ADDED
@@ -0,0 +1,36 @@
1
+ v0.6.0 (24th October 2011)
2
+ - Support for postgresql 8
3
+ v0.5.4 (17th October 2011)
4
+ - only clean files that were created by db2fog
5
+ v0.5.3 (12th October 2011)
6
+ - improve robustness of clean task
7
+ v0.5.2 (15th September 2011)
8
+ - fix restoring of postgresql databases (thanks Dan Neighman)
9
+ v0.5.1 (29th August 2011)
10
+ - relax fog dependency to allow higher versions
11
+ v0.5.0 (6th August 2011)
12
+ - initial postgresql support
13
+ - removed the statistics rake task
14
+ - fix an encoding issue under 1.9
15
+ v0.4.2 (24th July 2011)
16
+ - fixed recording of most recent backup
17
+ - remove runtime dependency on mysql2
18
+ v0.4.1 (23rd July 2011)
19
+ - fixed cleaning of old backups
20
+ v0.4.0 (22nd July 2011)
21
+ - forked db2s3 into db2fog
22
+ 0.3.1 (7 Dec 2009)
23
+ - Fixed hardcoded DB name in statistics task
24
+ 0.3.0 (6 Dec 2009)
25
+ - Added db2s3:backup:clean task to delete old backups
26
+ - Added dependency on activesupport for the clean task
27
+ 0.2.6 (6 Dec 2009)
28
+ - Remove metrics task, since it was far too inaccurate
29
+ - Add statistics task to show you the size of your tables
30
+ - Only add username to mysql command line if provided in database.yml
31
+ 0.2.5
32
+ - Use host provided in database.yml
33
+ 0.2.4 (2 Sep 2009)
34
+ - Fix credentials bug
35
+ 0.2.3
36
+ - Keep old backups around
@@ -0,0 +1,113 @@
1
+ = DB2Fog
2
+
3
+ A rails plugin to backup Mysql or PostgreSQL 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 is used 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
+ == Passing options to the database driver
57
+
58
+ Database adaptors may support further configuration via the :database_options hash.
59
+
60
+ DB2Fog.config = {
61
+ :directory => 'bucket-name',
62
+ :local_root => Rails.root.to_s + '/db/backups',
63
+ :provider => 'Local',
64
+ :database_options => {
65
+ :pg_version => 8
66
+ }
67
+ }
68
+
69
+ Supported database options:
70
+
71
+ pg_version 8 or 9
72
+ The major version of PostgreSQL installed on the server. Defaults to 9.
73
+
74
+ == Usage
75
+
76
+ # Add to your crontab or whatever
77
+ rake db2fog:full
78
+
79
+ # Handy tasks
80
+ rake db2fog:restore # You should be testing this regularly
81
+ rake db2fog:clean # Clean up old backups - cron this
82
+
83
+ === Alternative
84
+
85
+ If you want to trigger backups from ruby (say, from a delayed job) you can
86
+ do this:
87
+
88
+ DB2Fog.new.backup
89
+ DB2Fog.new.clean
90
+
91
+ == Compatibility
92
+
93
+ This is pure so ruby should run on most ruby VMs. I develop on MRI 1.9.2.
94
+
95
+ This will only work with rails 3. Supporting earlier versions is more
96
+ complication than I feel like handling at the moment.
97
+
98
+ == Development
99
+
100
+ Specs are a little weak and mysql based. This code is bit hackish but is being
101
+ used by quite a few people.
102
+
103
+ == Kudos
104
+
105
+ This is a fork of Xavier Shay's db2s3 gem. It worked perfectly, but only
106
+ supported Amazon S3 within US-east. By switching the dependency to using fog
107
+ this now supports all S3 regions and multiple storage providers
108
+
109
+ Xavier's original gem is available at https://github.com/xaviershay/db2s3
110
+
111
+ Xavier quotes the following example as inspiration:
112
+
113
+ http://github.com/pauldowman/blog_code_examples/tree/master/mysql_s3_backup
@@ -0,0 +1,227 @@
1
+ # coding: utf-8
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/class/attribute_accessors'
5
+ require 'active_support/core_ext/hash/except'
6
+ require 'fog'
7
+ require 'tempfile'
8
+ require 'db2fog/railtie'
9
+
10
+ class DB2Fog
11
+ cattr_accessor :config
12
+
13
+ def backup
14
+ file_name = "dump-#{db_credentials[:database]}-#{Time.now.utc.strftime("%Y%m%d%H%M")}.sql.gz"
15
+ store.store(file_name, open(database.dump))
16
+ store.store(most_recent_dump_file_name, file_name)
17
+ end
18
+
19
+ def restore
20
+ dump_file_name = store.fetch(most_recent_dump_file_name).read
21
+ file = store.fetch(dump_file_name)
22
+ database.restore(file.path)
23
+ end
24
+
25
+ def clean
26
+ to_keep = []
27
+ # only consider files that belong to db2fog. Other files are ignored
28
+ filelist = store.list.select {|file|
29
+ file.include?(db_credentials[:database]) && file.match(/\d{12}.sql.gz\Z/)
30
+ }
31
+ files = filelist.map { |file|
32
+ {
33
+ :path => file,
34
+ :date => Time.parse(file.split('-').last.split('.').first)
35
+ }
36
+ }
37
+ # Keep all backups from the past day
38
+ files.select {|x| x[:date] >= 1.day.ago }.each do |backup_for_day|
39
+ to_keep << backup_for_day
40
+ end
41
+
42
+ # Keep one backup per day from the last week
43
+ files.select {|x| x[:date] >= 1.week.ago }.group_by {|x| x[:date].strftime("%Y%m%d") }.values.each do |backups_for_last_week|
44
+ to_keep << backups_for_last_week.sort_by{|x| x[:date].strftime("%Y%m%d") }.first
45
+ end
46
+
47
+ # Keep one backup per week since forever
48
+ files.group_by {|x| x[:date].strftime("%Y%W") }.values.each do |backups_for_week|
49
+ to_keep << backups_for_week.sort_by{|x| x[:date].strftime("%Y%m%d") }.first
50
+ end
51
+
52
+ to_destroy = filelist - to_keep.uniq.collect {|x| x[:path] }
53
+ to_destroy.each do |file|
54
+ store.delete(file.split('/').last)
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def store
61
+ @store ||= FogStore.new
62
+ end
63
+
64
+ def most_recent_dump_file_name
65
+ "most-recent-dump-#{db_credentials[:database]}.txt"
66
+ end
67
+
68
+ def db_credentials
69
+ ActiveRecord::Base.connection.instance_eval { @config } # Dodgy!
70
+ end
71
+
72
+ def database
73
+ @database ||= case db_credentials[:adapter]
74
+ when /mysql/ then MysqlAdaptor.new(db_credentials)
75
+ when /postgres/ then PsqlAdaptor.new(db_credentials)
76
+ else
77
+ raise "database adaptor '#{db_credentials[:adapter]}' not supported"
78
+ end
79
+ end
80
+
81
+ class MysqlAdaptor
82
+
83
+ def initialize(credentials)
84
+ @credentials = credentials
85
+ end
86
+
87
+ def dump
88
+ dump_file = Tempfile.new("dump")
89
+
90
+ cmd = "mysqldump --quick --single-transaction --create-options #{mysql_options}"
91
+ cmd += " | gzip -9 > #{dump_file.path}"
92
+ run(cmd)
93
+
94
+ dump_file.path
95
+ end
96
+
97
+ def restore(path)
98
+ run "gunzip -c #{path} | mysql #{mysql_options}"
99
+ end
100
+
101
+ private
102
+
103
+ def mysql_options
104
+ cmd = ''
105
+ cmd += " -u #{@credentials[:username]} " unless @credentials[:username].nil?
106
+ cmd += " -p'#{@credentials[:password]}'" unless @credentials[:password].nil?
107
+ cmd += " -h '#{@credentials[:host]}'" unless @credentials[:host].nil?
108
+ cmd += " --default-character-set=#{@credentials[:encoding]}" unless @credentials[:encoding].nil?
109
+ cmd += " #{@credentials[:database]}"
110
+ end
111
+
112
+ def run(command)
113
+ result = system(command)
114
+ raise("error, process exited with status #{$?.exitstatus}") unless result
115
+ end
116
+
117
+ end
118
+
119
+ class PsqlAdaptor
120
+
121
+ def initialize(credentials)
122
+ @credentials = credentials
123
+ end
124
+
125
+ def dump
126
+ dump_file = Tempfile.new("dump")
127
+
128
+ cmd = "pg_dump --clean --format=p #{pg_dump_options}"
129
+ cmd += " | gzip -9 > #{dump_file.path}"
130
+ run(cmd)
131
+
132
+ dump_file.path
133
+ end
134
+
135
+ def restore(path)
136
+ run "gunzip -c #{path} | psql #{psql_options}"
137
+ end
138
+
139
+ private
140
+
141
+ def pg_dump_options
142
+ cmd = ''
143
+ cmd += " -U #{@credentials[:username]} " unless @credentials[:username].nil?
144
+ cmd += " -h '#{@credentials[:host]}'" unless @credentials[:host].nil?
145
+ cmd += " -w" if pg_version >= 9
146
+ cmd += " #{@credentials[:database]}"
147
+ end
148
+
149
+ def psql_options
150
+ cmd = ''
151
+ cmd += " -U #{@credentials[:username]} " unless @credentials[:username].nil?
152
+ cmd += " -h '#{@credentials[:host]}'" unless @credentials[:host].nil?
153
+ cmd += " -w" if pg_version >= 9
154
+ cmd += " -d #{@credentials[:database]}"
155
+ end
156
+
157
+ def pg_version
158
+ opts = database_options || {}
159
+ opts[:pg_version] || 9
160
+ end
161
+
162
+ def run(command)
163
+ result = system(command)
164
+ raise("error, process exited with status #{$?.exitstatus}") unless result
165
+ end
166
+
167
+ def database_options
168
+ if DB2Fog.config.respond_to?(:[])
169
+ DB2Fog.config[:database_options]
170
+ else
171
+ raise "DB2Fog not configured"
172
+ end
173
+ end
174
+
175
+ end
176
+
177
+ class FogStore
178
+
179
+ def store(remote_filename, io)
180
+ directory.files.create(:key => remote_filename, :body => io, :public => false)
181
+ end
182
+
183
+ def fetch(remote_filename)
184
+ remote_file = directory.files.get(remote_filename)
185
+
186
+ file = Tempfile.new("dump")
187
+ open(file.path, 'wb') { |f| f.write(remote_file.body) }
188
+ file
189
+ end
190
+
191
+ def list
192
+ directory.files.map { |f| f.key }
193
+ end
194
+
195
+ def delete(remote_filename)
196
+ remote_file = directory.files.head(remote_filename)
197
+ remote_file.destroy if remote_file
198
+ end
199
+
200
+ private
201
+
202
+ def fog_options
203
+ if DB2Fog.config.respond_to?(:[])
204
+ DB2Fog.config.except(:directory, :database_options)
205
+ else
206
+ raise "DB2Fog not configured"
207
+ end
208
+ end
209
+
210
+ def directory_name
211
+ if DB2Fog.config.respond_to?(:[])
212
+ DB2Fog.config[:directory]
213
+ else
214
+ raise "DB2Fog not configured"
215
+ end
216
+ end
217
+
218
+ def directory
219
+ @directory ||= storage.directories.get(directory_name)
220
+ end
221
+
222
+ def storage
223
+ @storage = Fog::Storage.new(fog_options)
224
+ end
225
+ end
226
+
227
+ end
@@ -0,0 +1,11 @@
1
+ require 'rails'
2
+ require 'db2fog'
3
+
4
+ class DB2Fog
5
+ class Railtie < Rails::Railtie
6
+
7
+ rake_tasks do
8
+ load File.expand_path('tasks.rb', File.dirname(__FILE__))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+
3
+ namespace :db2fog do
4
+ desc "Save a full back to S3"
5
+ task :backup => :environment do
6
+ DB2Fog.new.backup
7
+ end
8
+
9
+ desc "Restore your DB from S3"
10
+ task :restore => :environment do
11
+ DB2Fog.new.restore
12
+ end
13
+
14
+ desc "Keep all backups for the last day, one per day for the last week, and one per week before that. Delete the rest."
15
+ task :clean => :environment do
16
+ DB2Fog.new.clean
17
+ end
18
+
19
+ namespace :backup do
20
+ task :full => :environment do
21
+ $stderr.puts "the db2fog:backup:full rake task is deprecated, use db2fog:backup instead"
22
+ DB2Fog.new.backup
23
+ end
24
+
25
+ task :restore => :environment do
26
+ $stderr.puts "the db2fog:backup:restore rake task is deprecated, use db2fog:restore instead"
27
+ DB2Fog.new.restore
28
+ end
29
+
30
+ task :clean => :environment do
31
+ $stderr.puts "the db2fog:backup:clean rake task is deprecated, use db2fog:clean instead"
32
+ DB2Fog.new.clean
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,179 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ray-db2fog
3
+ version: !ruby/object:Gem::Version
4
+ hash: 1
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 7
9
+ - 1
10
+ version: 0.7.1
11
+ platform: ruby
12
+ authors:
13
+ - James Healy, Sasan Padidar
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-04 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rails
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 7
29
+ segments:
30
+ - 3
31
+ - 0
32
+ version: "3.0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: activerecord
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 7
44
+ segments:
45
+ - 3
46
+ - 0
47
+ version: "3.0"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: fog
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ hash: 15
59
+ segments:
60
+ - 1
61
+ - 0
62
+ version: "1.0"
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: rake
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ type: :development
78
+ version_requirements: *id004
79
+ - !ruby/object:Gem::Dependency
80
+ name: mysql2
81
+ prerelease: false
82
+ requirement: &id005 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ type: :development
92
+ version_requirements: *id005
93
+ - !ruby/object:Gem::Dependency
94
+ name: rspec
95
+ prerelease: false
96
+ requirement: &id006 !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ hash: 15
102
+ segments:
103
+ - 2
104
+ - 6
105
+ version: "2.6"
106
+ type: :development
107
+ version_requirements: *id006
108
+ - !ruby/object:Gem::Dependency
109
+ name: timecop
110
+ prerelease: false
111
+ requirement: &id007 !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ~>
115
+ - !ruby/object:Gem::Version
116
+ hash: 25
117
+ segments:
118
+ - 0
119
+ - 3
120
+ - 5
121
+ version: 0.3.5
122
+ type: :development
123
+ version_requirements: *id007
124
+ description: ray-db2fog was forked from https://github.com/yob/db2fog.git.
125
+ email:
126
+ - spadidar@raybeam.com
127
+ executables: []
128
+
129
+ extensions: []
130
+
131
+ extra_rdoc_files: []
132
+
133
+ files:
134
+ - lib/db2fog.rb
135
+ - lib/db2fog/tasks.rb
136
+ - lib/db2fog/railtie.rb
137
+ - README.rdoc
138
+ - HISTORY
139
+ homepage: http://github.com/spadidar/db2fog
140
+ licenses: []
141
+
142
+ post_install_message:
143
+ rdoc_options:
144
+ - --title
145
+ - DB2Fog
146
+ - --line-numbers
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ hash: 57
155
+ segments:
156
+ - 1
157
+ - 8
158
+ - 7
159
+ version: 1.8.7
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ hash: 31
166
+ segments:
167
+ - 1
168
+ - 3
169
+ - 2
170
+ version: 1.3.2
171
+ requirements: []
172
+
173
+ rubyforge_project:
174
+ rubygems_version: 1.8.10
175
+ signing_key:
176
+ specification_version: 3
177
+ summary: db2fog provides rake tasks for backing up and restoring your DB to cloud storage providers
178
+ test_files: []
179
+