rayback 0.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,228 @@
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 *args
88
+ options = args.extract_options!
89
+
90
+ dump_file = Tempfile.new("dump")
91
+ flags = "--quick --single-transaction --create-options"
92
+ cmd = "mysqldump #{flags} #{mysql_options} #{options[:database]} " + options[:tables].join(" ")
93
+ cmd += " | gzip -9 > #{dump_file.path}"
94
+ run(cmd)
95
+
96
+ dump_file.path
97
+ end
98
+
99
+ def restore(path)
100
+ run "gunzip -c #{path} | mysql #{mysql_options}"
101
+ end
102
+
103
+ private
104
+
105
+ def mysql_options
106
+ cmd = ''
107
+ cmd += " -u #{@credentials[:username]} " unless @credentials[:username].nil?
108
+ cmd += " -p'#{@credentials[:password]}'" unless @credentials[:password].nil?
109
+ cmd += " -h '#{@credentials[:host]}'" unless @credentials[:host].nil?
110
+ cmd += " --default-character-set=#{@credentials[:encoding]}" unless @credentials[:encoding].nil?
111
+ end
112
+
113
+ def run(command)
114
+ result = system(command)
115
+ raise("error, process exited with status #{$?.exitstatus}") unless result
116
+ end
117
+
118
+ end
119
+
120
+ class PsqlAdaptor
121
+
122
+ def initialize(credentials)
123
+ @credentials = credentials
124
+ end
125
+
126
+ def dump
127
+ dump_file = Tempfile.new("dump")
128
+
129
+ cmd = "pg_dump --clean --format=p #{pg_dump_options}"
130
+ cmd += " | gzip -9 > #{dump_file.path}"
131
+ run(cmd)
132
+
133
+ dump_file.path
134
+ end
135
+
136
+ def restore(path)
137
+ run "gunzip -c #{path} | psql #{psql_options}"
138
+ end
139
+
140
+ private
141
+
142
+ def pg_dump_options
143
+ cmd = ''
144
+ cmd += " -U #{@credentials[:username]} " unless @credentials[:username].nil?
145
+ cmd += " -h '#{@credentials[:host]}'" unless @credentials[:host].nil?
146
+ cmd += " -w" if pg_version >= 9
147
+ cmd += " #{@credentials[:database]}"
148
+ end
149
+
150
+ def psql_options
151
+ cmd = ''
152
+ cmd += " -U #{@credentials[:username]} " unless @credentials[:username].nil?
153
+ cmd += " -h '#{@credentials[:host]}'" unless @credentials[:host].nil?
154
+ cmd += " -w" if pg_version >= 9
155
+ cmd += " -d #{@credentials[:database]}"
156
+ end
157
+
158
+ def pg_version
159
+ opts = database_options || {}
160
+ opts[:pg_version] || 9
161
+ end
162
+
163
+ def run(command)
164
+ result = system(command)
165
+ raise("error, process exited with status #{$?.exitstatus}") unless result
166
+ end
167
+
168
+ def database_options
169
+ if DB2Fog.config.respond_to?(:[])
170
+ DB2Fog.config[:database_options]
171
+ else
172
+ raise "DB2Fog not configured"
173
+ end
174
+ end
175
+
176
+ end
177
+
178
+ class FogStore
179
+
180
+ def store(remote_filename, io)
181
+ directory.files.create(:key => remote_filename, :body => io, :public => false)
182
+ end
183
+
184
+ def fetch(remote_filename)
185
+ remote_file = directory.files.get(remote_filename)
186
+
187
+ file = Tempfile.new("dump")
188
+ open(file.path, 'wb') { |f| f.write(remote_file.body) }
189
+ file
190
+ end
191
+
192
+ def list
193
+ directory.files.map { |f| f.key }
194
+ end
195
+
196
+ def delete(remote_filename)
197
+ remote_file = directory.files.head(remote_filename)
198
+ remote_file.destroy if remote_file
199
+ end
200
+
201
+ private
202
+
203
+ def fog_options
204
+ if DB2Fog.config.respond_to?(:[])
205
+ DB2Fog.config.except(:directory, :database_options)
206
+ else
207
+ raise "DB2Fog not configured"
208
+ end
209
+ end
210
+
211
+ def directory_name
212
+ if DB2Fog.config.respond_to?(:[])
213
+ DB2Fog.config[:directory]
214
+ else
215
+ raise "DB2Fog not configured"
216
+ end
217
+ end
218
+
219
+ def directory
220
+ @directory ||= storage.directories.get(directory_name)
221
+ end
222
+
223
+ def storage
224
+ @storage = Fog::Storage.new(fog_options)
225
+ end
226
+ end
227
+
228
+ 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,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rayback
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Sasan Padidar
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-01-05 00:00:00 Z
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
+ hash: 7
28
+ segments:
29
+ - 3
30
+ - 0
31
+ version: "3.0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: activerecord
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ hash: 7
43
+ segments:
44
+ - 3
45
+ - 0
46
+ version: "3.0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: fog
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ hash: 15
58
+ segments:
59
+ - 1
60
+ - 0
61
+ version: "1.0"
62
+ type: :runtime
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: rake
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ type: :development
77
+ version_requirements: *id004
78
+ - !ruby/object:Gem::Dependency
79
+ name: mysql2
80
+ prerelease: false
81
+ requirement: &id005 !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ type: :development
91
+ version_requirements: *id005
92
+ - !ruby/object:Gem::Dependency
93
+ name: rspec
94
+ prerelease: false
95
+ requirement: &id006 !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ~>
99
+ - !ruby/object:Gem::Version
100
+ hash: 15
101
+ segments:
102
+ - 2
103
+ - 6
104
+ version: "2.6"
105
+ type: :development
106
+ version_requirements: *id006
107
+ - !ruby/object:Gem::Dependency
108
+ name: timecop
109
+ prerelease: false
110
+ requirement: &id007 !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ~>
114
+ - !ruby/object:Gem::Version
115
+ hash: 25
116
+ segments:
117
+ - 0
118
+ - 3
119
+ - 5
120
+ version: 0.3.5
121
+ type: :development
122
+ version_requirements: *id007
123
+ description: rayback was forked from https://github.com/yob/db2fog.git.
124
+ email:
125
+ - spadidar@raybeam.com
126
+ executables: []
127
+
128
+ extensions: []
129
+
130
+ extra_rdoc_files: []
131
+
132
+ files:
133
+ - lib/db2fog.rb
134
+ - lib/db2fog/tasks.rb
135
+ - lib/db2fog/railtie.rb
136
+ - README.rdoc
137
+ - HISTORY
138
+ homepage: http://github.com/spadidar/db2fog
139
+ licenses: []
140
+
141
+ post_install_message:
142
+ rdoc_options:
143
+ - --title
144
+ - DB2Fog
145
+ - --line-numbers
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ hash: 57
154
+ segments:
155
+ - 1
156
+ - 8
157
+ - 7
158
+ version: 1.8.7
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ none: false
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ hash: 31
165
+ segments:
166
+ - 1
167
+ - 3
168
+ - 2
169
+ version: 1.3.2
170
+ requirements: []
171
+
172
+ rubyforge_project:
173
+ rubygems_version: 1.8.10
174
+ signing_key:
175
+ specification_version: 3
176
+ summary: Rayback provides rake tasks for backing up and restoring your DB to cloud storage providers
177
+ test_files: []
178
+