sauberia-db2s3 0.3.2.1

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/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ spec/s3_config.rb
2
+ pkg
data/HISTORY ADDED
@@ -0,0 +1,15 @@
1
+ 0.3.1 (7 Dec 2009)
2
+ - Fixed hardcoded DB name in statistics task
3
+ 0.3.0 (6 Dec 2009)
4
+ - Added db2s3:backup:clean task to delete old backups
5
+ - Added dependency on activesupport for the clean task
6
+ 0.2.6 (6 Dec 2009)
7
+ - Remove metrics task, since it was far too inaccurate
8
+ - Add statistics task to show you the size of your tables
9
+ - Only add username to mysql command line if provided in database.yml
10
+ 0.2.5
11
+ - Use host provided in database.yml
12
+ 0.2.4 (2 Sep 2009)
13
+ - Fix credentials bug
14
+ 0.2.3
15
+ - Keep old backups around
data/README.rdoc ADDED
@@ -0,0 +1,40 @@
1
+ = DB2S3
2
+
3
+ A rails plugin to backup Mysql to Amazon S3. You're looking at a monthly spend of four cents. So pony up you cheap bastard, and store your backups on S3
4
+
5
+ == Usage
6
+
7
+ # In config/environment.rb
8
+ config.gem "db2s3", :source => "http://gemcutter.org"
9
+
10
+ # In Rakefile
11
+ require 'db2s3/tasks'
12
+
13
+ # In config/initializers/db2s3.rb
14
+ DB2S3::Config.instance_eval do
15
+ S3 = {
16
+ :access_key_id => 'yourkey',
17
+ :secret_access_key => 'yoursecretkey',
18
+ :bucket => 'yourapp-db-backup'
19
+ }
20
+ end
21
+ # DB credentials are read from your rails environment
22
+
23
+ rake gems:install
24
+
25
+ # Add to your crontab or whatever
26
+ rake db2s3:backup:full
27
+ rake db2s3:backup:incremental # Unimplemented
28
+
29
+ # Handy tasks
30
+ rake db2s3:statistics # Shows you the size of your DB
31
+ rake db2s3:backup:restore # You should be testing this regularly
32
+ rake db2s3:backup:clean # Clean up old backups - cron this
33
+
34
+ == Development
35
+
36
+ Specs are really week. This code is bit hackish but is being used by quite a few people.
37
+
38
+ == Kudos
39
+
40
+ http://github.com/pauldowman/blog_code_examples/tree/master/mysql_s3_backup
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ desc "Create test database"
2
+ task :create_test_db do
3
+ `mysqladmin -u root create db2s3_unittest`
4
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.2
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1 @@
1
+ Dir["#{File.dirname(__FILE__)}/../../tasks/*.rake"].each { |ext| load ext }
data/lib/db2s3.rb ADDED
@@ -0,0 +1,177 @@
1
+ begin
2
+ require 'active_support' # The new one
3
+ rescue LoadError
4
+ require 'activesupport' # The old one
5
+ end
6
+ require 's3'
7
+ require 'tempfile'
8
+
9
+ class DB2S3
10
+ class Config
11
+ end
12
+
13
+ def initialize
14
+ end
15
+
16
+ def full_backup
17
+ file_name = dump_file_name(Time.now)
18
+ store.store(file_name, open(dump_db.path))
19
+ store.store(most_recent_dump_file_name, file_name)
20
+ end
21
+
22
+ def restore
23
+ dump_file_name = store.fetch(most_recent_dump_file_name).read
24
+ file = store.fetch(dump_file_name)
25
+ run "gunzip -c #{file.path} | mysql #{mysql_options}"
26
+ end
27
+
28
+ # TODO: This method really needs specs
29
+ def clean
30
+ files = file_objects_from_paths(store.list("#{dump_file_name_prefix}-"))
31
+ determine_what_to_keep files
32
+ delete_surplus_backups files
33
+ end
34
+
35
+ def file_objects_from_paths paths
36
+ paths.collect do |path| {
37
+ :path => path,
38
+ :date => Time.parse(path.split('-').last.split('.').first),
39
+ :keep => false
40
+ }
41
+ end
42
+ end
43
+
44
+ def determine_what_to_keep files
45
+ # Keep all backups from the past day
46
+ files.select {|x| x[:date] >= 1.day.ago }.map! do |backup_for_day|
47
+ backup_for_day[:keep] = true
48
+ end
49
+ # Keep one backup per day from the last week
50
+ files.select {|x| x[:date] >= 1.week.ago }.group_by {|x| x[:date].strftime("%u") }.values.map! do |backups_for_last_week|
51
+ backups_for_last_week.sort_by{|x| x[:path] }.first[:keep] = true
52
+ end
53
+ # Keep one backup per week from the last 28 days
54
+ files.select {|x| x[:date] >= 28.days.ago }.group_by {|x| x[:date].strftime("%Y%W") }.values.map! do |backups_for_last_28_days|
55
+ backups_for_last_28_days.sort_by{|x| x[:path] }.first[:keep] = true
56
+ end
57
+ # Keep one backup per month since forever
58
+ files.group_by {|x| x[:date].strftime("%Y%m") }.values.map! do |backups_for_month|
59
+ backups_for_month.sort_by{|x| x[:path] }.first[:keep] = true
60
+ end
61
+ end
62
+
63
+ def delete_surplus_backups files
64
+ files.each do |file|
65
+ store.delete(file[:path]) unless file[:keep]
66
+ end
67
+ end
68
+
69
+ def statistics
70
+ # From http://mysqlpreacher.com/wordpress/tag/table-size/
71
+ results = ActiveRecord::Base.connection.execute(<<-EOS)
72
+ SELECT
73
+ engine,
74
+ ROUND(data_length/1024/1024,2) total_size_mb,
75
+ ROUND(index_length/1024/1024,2) total_index_size_mb,
76
+ table_rows,
77
+ table_name article_attachment
78
+ FROM information_schema.tables
79
+ WHERE table_schema = '#{db_credentials[:database]}'
80
+ ORDER BY total_size_mb + total_index_size_mb desc;
81
+ EOS
82
+ rows = []
83
+ results.each {|x| rows << x.to_a }
84
+ rows
85
+ end
86
+
87
+ private
88
+
89
+ def dump_db
90
+ dump_file = Tempfile.new('dump')
91
+ cmd = "mysqldump --quick --single-transaction --create-options #{mysql_options}"
92
+ cmd += " | gzip > #{dump_file.path}"
93
+ run(cmd)
94
+
95
+ dump_file
96
+ end
97
+
98
+ def mysql_options
99
+ cmd = ''
100
+ cmd += " -u #{db_credentials[:username]} " unless db_credentials[:username].nil?
101
+ cmd += " -p'#{db_credentials[:password]}'" unless db_credentials[:password].nil?
102
+ cmd += " -h '#{db_credentials[:host]}'" unless db_credentials[:host].nil?
103
+ cmd += " #{db_credentials[:database]}"
104
+ end
105
+
106
+ def store
107
+ @store ||= S3Store.new
108
+ end
109
+
110
+ def dump_file_name_prefix
111
+ "dump-#{db_credentials[:database]}"
112
+ end
113
+
114
+ def dump_file_name time
115
+ "#{dump_file_name_prefix}-#{time.utc.strftime("%Y%m%d%H%M%S")}.sql.gz"
116
+ end
117
+
118
+ def most_recent_dump_file_name
119
+ "most-recent-#{dump_file_name_prefix}.txt"
120
+ end
121
+
122
+ def run(command)
123
+ result = system(command)
124
+ raise("error, process exited with status #{$?.exitstatus}") unless result
125
+ end
126
+
127
+ def db_credentials
128
+ ActiveRecord::Base.connection.instance_eval { @config } # Dodgy!
129
+ end
130
+
131
+ class S3Store
132
+ def initialize
133
+ @connected = false
134
+ end
135
+
136
+ def ensure_connected
137
+ return if @connected
138
+ s3_service = S3::Service.new(DB2S3::Config::S3.slice(:access_key_id, :secret_access_key).merge(:use_ssl => true))
139
+ @bucket = s3_service.buckets.build(DB2S3::Config::S3[:bucket])
140
+ @connected = true
141
+ end
142
+
143
+ def store(file_name, file)
144
+ ensure_connected
145
+ object = bucket.objects.build(file_name)
146
+ object.content = file.class == String ? file : (file.rewind; file.read)
147
+ object.save
148
+ end
149
+
150
+ def fetch(file_name)
151
+ ensure_connected
152
+ file = Tempfile.new('dump')
153
+ file.binmode if file.respond_to?(:binmode)
154
+ file.write(bucket.objects.find(file_name).content)
155
+ file.rewind
156
+ file
157
+ end
158
+
159
+ def list(prefix)
160
+ ensure_connected
161
+ bucket.objects.find_all(:prefix => prefix).collect {|x| x.key }
162
+ end
163
+
164
+ def delete(file_name)
165
+ if object = bucket.objects.find(file_name)
166
+ object.delete
167
+ end
168
+ end
169
+
170
+ private
171
+
172
+ def bucket
173
+ @bucket
174
+ end
175
+ end
176
+
177
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/db2s3')
@@ -0,0 +1,26 @@
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[:username]} #{DBConfig[:database]}`
6
+ end
7
+
8
+ def drop_schema
9
+ `cat '#{File.dirname(__FILE__) + '/mysql_drop_schema.sql'}' | mysql -u #{DBConfig[:username]} #{DBConfig[:database]}`
10
+ end
11
+
12
+ class Person < ActiveRecord::Base
13
+ end
14
+
15
+ if DB2S3::Config.const_defined?('S3')
16
+ it 'can save and restore a backup to S3' do
17
+ db2s3 = DB2S3.new
18
+ load_schema
19
+ Person.create!(:name => "Baxter")
20
+ db2s3.full_backup
21
+ drop_schema
22
+ db2s3.restore
23
+ Person.find_by_name("Baxter").should_not be_nil
24
+ end
25
+ end
26
+ end
@@ -0,0 +1 @@
1
+ DROP TABLE IF EXISTS people;
@@ -0,0 +1,4 @@
1
+ DROP TABLE IF EXISTS people;
2
+ CREATE TABLE people (
3
+ name VARCHAR(255) NULL
4
+ );
@@ -0,0 +1,5 @@
1
+ DB2S3::Config:: S3 = {
2
+ :access_key_id => 'yourkey',
3
+ :secret_access_key => 'yoursecretkey',
4
+ :bucket => 'db2s3_test'
5
+ }
@@ -0,0 +1,24 @@
1
+ require 'spec'
2
+
3
+ begin
4
+ require 'active_record' # The new one
5
+ rescue LoadError
6
+ require 'activerecord' # The old one
7
+ end
8
+
9
+ require File.dirname(__FILE__) + '/../lib/db2s3'
10
+ if File.exists?(File.dirname(__FILE__) + '/s3_config.rb')
11
+ require File.dirname(__FILE__) + '/s3_config.rb'
12
+ else
13
+ puts "s3_config.rb does not exist - not running live tests"
14
+ end
15
+
16
+ DBConfig = {
17
+ :adapter => "mysql",
18
+ :encoding => "utf8",
19
+ :database => 'db2s3_unittest',
20
+ :username => "root"
21
+ }
22
+
23
+ ActiveRecord::Base.configurations = { 'production' => DBConfig }
24
+ ActiveRecord::Base.establish_connection(:production)
data/tasks/tasks.rake ADDED
@@ -0,0 +1,26 @@
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
+
8
+ desc "Restore your DB from S3"
9
+ task :restore => :environment do
10
+ DB2S3.new.restore
11
+ end
12
+
13
+ desc "Keep all backups for the last day, one per day for the last week, one per week for the last 28 days, and one per month before that. Delete the rest."
14
+ task :clean => :environment do
15
+ DB2S3.new.clean
16
+ end
17
+ end
18
+
19
+ desc "Show table sizes for your database"
20
+ task :statistics => :environment do
21
+ rows = DB2S3.new.statistics
22
+ rows.sort_by {|x| -x[3].to_i }
23
+ header = [["Type", "Data MB", "Index", "Rows", "Name"], []]
24
+ puts (header + rows).collect {|x| x.join("\t") }
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sauberia-db2s3
3
+ version: !ruby/object:Gem::Version
4
+ hash: 93
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 2
10
+ - 1
11
+ version: 0.3.2.1
12
+ platform: ruby
13
+ authors:
14
+ - Xavier Shay
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-10-22 00:00:00 +01:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: s3
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 29
31
+ segments:
32
+ - 0
33
+ - 3
34
+ - 7
35
+ version: 0.3.7
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ description: db2s3 provides rake tasks for backing up and restoring your DB to S3
39
+ email: contact@rhnh.net
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - README.rdoc
46
+ files:
47
+ - .gitignore
48
+ - HISTORY
49
+ - README.rdoc
50
+ - Rakefile
51
+ - VERSION
52
+ - init.rb
53
+ - lib/db2s3.rb
54
+ - lib/db2s3/tasks.rb
55
+ - rails/init.rb
56
+ - spec/db2s3_spec.rb
57
+ - spec/mysql_drop_schema.sql
58
+ - spec/mysql_schema.sql
59
+ - spec/s3_config.example.rb
60
+ - spec/spec_helper.rb
61
+ - tasks/tasks.rake
62
+ has_rdoc: true
63
+ homepage: http://github.com/xaviershay/db2s3
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --charset=UTF-8
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project:
92
+ rubygems_version: 1.3.7
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Summarize your gem
96
+ test_files:
97
+ - spec/db2s3_spec.rb
98
+ - spec/s3_config.example.rb
99
+ - spec/spec_helper.rb