dumper 0.0.3 → 0.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/.gitignore +0 -15
- data/.rspec +1 -0
- data/README.md +20 -12
- data/Rakefile +5 -0
- data/dumper.gemspec +13 -9
- data/lib/dumper.rb +12 -5
- data/lib/dumper/agent.rb +112 -0
- data/lib/dumper/cli.rb +3 -0
- data/lib/dumper/database/base.rb +9 -0
- data/lib/dumper/database/mysql.rb +22 -0
- data/lib/dumper/dependency.rb +24 -0
- data/lib/dumper/job.rb +103 -0
- data/lib/dumper/stack.rb +69 -0
- data/lib/dumper/utility.rb +33 -0
- data/lib/dumper/version.rb +1 -1
- data/spec/dumper_spec.rb +7 -0
- data/spec/spec_helper.rb +7 -0
- metadata +54 -10
data/.gitignore
CHANGED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/README.md
CHANGED
@@ -1,19 +1,27 @@
|
|
1
|
-
# Dumper
|
1
|
+
# Dumper Agent for Rails
|
2
2
|
|
3
|
-
|
3
|
+
Dumper is a backup management system that offers a whole new way to take daily backups of your databases.
|
4
4
|
|
5
|
-
|
5
|
+
** This app will be launched soon! **
|
6
|
+
|
7
|
+
## Supported Stacks
|
8
|
+
|
9
|
+
* Ruby 1.8.7 , Ruby 1.9.2 or later
|
10
|
+
* Rails 3.0 or later
|
11
|
+
* MySQL with ActiveRecord
|
6
12
|
|
7
|
-
|
13
|
+
Support for PostgreSQL, MongoDB and Redis are coming soon.
|
14
|
+
|
15
|
+
## Installation
|
8
16
|
|
9
|
-
|
17
|
+
Add the following line to your Rails project Gemfile:
|
10
18
|
|
11
|
-
|
19
|
+
```ruby
|
20
|
+
gem 'dumper'
|
21
|
+
```
|
12
22
|
|
13
|
-
|
23
|
+
then create `config/initializers/dumper.rb` and put the following line.
|
14
24
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
19
|
-
5. Create new Pull Request
|
25
|
+
```ruby
|
26
|
+
Dumper::Agent.start(:app_key => 'YOUR_APP_KEY')
|
27
|
+
```
|
data/Rakefile
CHANGED
data/dumper.gemspec
CHANGED
@@ -2,19 +2,23 @@
|
|
2
2
|
require File.expand_path('../lib/dumper/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors = [
|
6
|
-
gem.email = [
|
7
|
-
gem.description =
|
8
|
-
gem.summary =
|
9
|
-
gem.homepage =
|
5
|
+
gem.authors = ['Kenn Ejima']
|
6
|
+
gem.email = ['kenn.ejima@gmail.com']
|
7
|
+
gem.description = 'Dumper is a backup management system that offers a whole new way to take daily backups of your databases. (coming soon!)'
|
8
|
+
gem.summary = 'The Dumper Agent for Rails (coming soon!)'
|
9
|
+
gem.homepage = 'https://github.com/kenn/dumper'
|
10
10
|
|
11
11
|
gem.files = `git ls-files`.split($\)
|
12
12
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
13
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
-
gem.name =
|
15
|
-
gem.require_paths = [
|
14
|
+
gem.name = 'dumper'
|
15
|
+
gem.require_paths = ['lib']
|
16
16
|
gem.version = Dumper::VERSION
|
17
17
|
|
18
|
-
gem.add_runtime_dependency
|
19
|
-
gem.add_runtime_dependency
|
18
|
+
gem.add_runtime_dependency 'multi_json', '~> 1.0'
|
19
|
+
gem.add_runtime_dependency 'posix-spawn', '~> 0.3.6'
|
20
|
+
gem.add_development_dependency 'rspec'
|
21
|
+
|
22
|
+
# For Travis
|
23
|
+
gem.add_development_dependency 'rake'
|
20
24
|
end
|
data/lib/dumper.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
-
require 'thor'
|
2
|
-
require 'rainbow'
|
3
1
|
require 'socket'
|
4
2
|
|
5
3
|
module Dumper
|
6
|
-
autoload :
|
7
|
-
autoload :
|
8
|
-
autoload :
|
4
|
+
autoload :Agent, 'dumper/agent'
|
5
|
+
autoload :Cli, 'dumper/cli'
|
6
|
+
autoload :Dependency, 'dumper/dependency'
|
7
|
+
autoload :Job, 'dumper/job'
|
8
|
+
autoload :Stack, 'dumper/stack'
|
9
|
+
autoload :Utility, 'dumper/utility'
|
10
|
+
autoload :VERSION, 'dumper/version'
|
11
|
+
|
12
|
+
module Database
|
13
|
+
autoload :Base, 'dumper/database/base'
|
14
|
+
autoload :MySQL, 'dumper/database/mysql'
|
15
|
+
end
|
9
16
|
end
|
data/lib/dumper/agent.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Dumper
|
5
|
+
class Agent
|
6
|
+
include Dumper::Utility::LoggingMethods
|
7
|
+
|
8
|
+
API_VERSION = 1
|
9
|
+
|
10
|
+
attr_reader :stack
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def start(options = {})
|
14
|
+
new(options).start
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_if(options = {})
|
18
|
+
start(options) if yield
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(options = {})
|
23
|
+
log '**** Dumper requires :app_key! ****' if options[:app_key].blank?
|
24
|
+
|
25
|
+
@stack = Dumper::Stack.new
|
26
|
+
@api_base = options[:api_base] || 'http://dumper.io'
|
27
|
+
@app_key = options[:app_key]
|
28
|
+
@app_env = @stack.rails_env
|
29
|
+
@app_name = ObjectSpace.each_object(Rails::Application).first.class.name.split("::").first
|
30
|
+
end
|
31
|
+
|
32
|
+
def start
|
33
|
+
return unless @app_key and @stack.supported?
|
34
|
+
log "stack: dispatcher = #{@stack.dispatcher}, framework = #{@stack.framework}, rackup = #{@stack.rackup}"
|
35
|
+
|
36
|
+
@loop_thread = Thread.new { start_loop }
|
37
|
+
@loop_thread[:name] = 'Loop Thread'
|
38
|
+
end
|
39
|
+
|
40
|
+
def start_loop
|
41
|
+
sleep 3
|
42
|
+
json = send_request(api: 'agent/register', json: MultiJson.encode(register_hash))
|
43
|
+
@token = json[:token] if json && json[:token]
|
44
|
+
sleep 3
|
45
|
+
loop do
|
46
|
+
json = send_request(api: 'agent/poll', params: { token: @token })
|
47
|
+
if json
|
48
|
+
# Promoted or demoted?
|
49
|
+
if json[:token]
|
50
|
+
@token = json[:token] # promote
|
51
|
+
else
|
52
|
+
@token = nil # demote
|
53
|
+
end
|
54
|
+
|
55
|
+
if json[:job]
|
56
|
+
if pid = fork
|
57
|
+
# Parent
|
58
|
+
srand # Ruby 1.8.7 needs reseeding - http://bugs.ruby-lang.org/issues/4338
|
59
|
+
Process.detach(pid)
|
60
|
+
else
|
61
|
+
# Child
|
62
|
+
Dumper::Job.new(self, json[:job]).run_and_exit
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
sleep @token ? 60.seconds : 1.hour
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def register_hash
|
71
|
+
{
|
72
|
+
# :pid => Process.pid,
|
73
|
+
# :host => Socket.gethostname,
|
74
|
+
:agent_version => Dumper::VERSION,
|
75
|
+
:app_name => @app_name,
|
76
|
+
:stack => @stack.to_hash,
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def send_request(options)
|
81
|
+
uri = URI.parse("#{@api_base}/api/#{options[:api]}")
|
82
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
83
|
+
if uri.is_a? URI::HTTPS
|
84
|
+
http.use_ssl = true
|
85
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
86
|
+
end
|
87
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
88
|
+
request['x-app-key'] = @app_key
|
89
|
+
request['x-app-env'] = @app_env
|
90
|
+
request['x-api-version'] = API_VERSION.to_s
|
91
|
+
request['user-agent'] = "Dumper-RailsAgent/#{Dumper::VERSION} (ruby #{::RUBY_VERSION} #{::RUBY_PLATFORM} / rails #{Rails::VERSION::STRING})"
|
92
|
+
if options[:params]
|
93
|
+
request.set_form_data(options[:params])
|
94
|
+
else
|
95
|
+
# Without empty string, WEBrick would complain WEBrick::HTTPStatus::LengthRequired for empty POSTs
|
96
|
+
request.body = options[:json] || ''
|
97
|
+
request['Content-Type'] = 'application/octet-stream'
|
98
|
+
end
|
99
|
+
|
100
|
+
response = http.request(request)
|
101
|
+
if response.code == '200'
|
102
|
+
log response.body, :debug
|
103
|
+
else
|
104
|
+
log '******** ERROR!! ********', :error
|
105
|
+
end
|
106
|
+
MultiJson.decode(response.body).with_indifferent_access
|
107
|
+
rescue
|
108
|
+
log_last_error
|
109
|
+
{} # return empty hash
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/dumper/cli.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Dumper
|
2
|
+
module Database
|
3
|
+
class MySQL < Base
|
4
|
+
def command
|
5
|
+
"mysqldump #{connection_options} #{additional_options} #{@stack.activerecord_config['database']} | gzip"
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def connection_options
|
11
|
+
[ :host, :port, :username, :password ].map do |option|
|
12
|
+
next if @stack.activerecord_config[option.to_s].blank?
|
13
|
+
"--#{option}='#{@stack.activerecord_config[option.to_s]}'".gsub('--username', '--user')
|
14
|
+
end.compact.join(' ')
|
15
|
+
end
|
16
|
+
|
17
|
+
def additional_options
|
18
|
+
'--single-transaction'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Dumper
|
2
|
+
class Dependency
|
3
|
+
LIBS = {
|
4
|
+
'thor' => { :require => 'thor', :version => '~> 0.14.0' },
|
5
|
+
'rainbow' => { :require => 'rainbow', :version => '~> 1.1.4' },
|
6
|
+
}
|
7
|
+
|
8
|
+
def self.load(name)
|
9
|
+
begin
|
10
|
+
gem(name, LIBS[name][:version])
|
11
|
+
require(LIBS[name][:require])
|
12
|
+
rescue LoadError
|
13
|
+
abort <<-EOS
|
14
|
+
Dependency missing: #{name}
|
15
|
+
To install the gem, issue the following command:
|
16
|
+
|
17
|
+
gem install #{name} -v '#{LIBS[name][:version]}'
|
18
|
+
|
19
|
+
Please try again after installing the missing dependency.
|
20
|
+
EOS
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/dumper/job.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'posix/spawn'
|
2
|
+
|
3
|
+
module Dumper
|
4
|
+
class Job
|
5
|
+
include POSIX::Spawn
|
6
|
+
include Dumper::Utility::LoggingMethods
|
7
|
+
|
8
|
+
def initialize(agent, job)
|
9
|
+
@agent = agent
|
10
|
+
@stack = agent.stack
|
11
|
+
@job = job
|
12
|
+
end
|
13
|
+
|
14
|
+
def run_and_exit
|
15
|
+
@job[:servers].each do |server|
|
16
|
+
perform(server)
|
17
|
+
end
|
18
|
+
ensure
|
19
|
+
log_last_error if $!
|
20
|
+
log 'exiting...'
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
def perform(server)
|
25
|
+
# Prepare
|
26
|
+
json = @agent.send_request(api: 'backup/prepare', params: { server_id: server[:id], manual: server[:manual].to_s })
|
27
|
+
return unless json[:status] == 'ok'
|
28
|
+
|
29
|
+
case server[:type]
|
30
|
+
when 'mysql'
|
31
|
+
@database = Dumper::Database::MySQL.new(@stack)
|
32
|
+
else
|
33
|
+
abort 'invalid server type!' # TBD
|
34
|
+
end
|
35
|
+
backup_id = json[:backup][:id]
|
36
|
+
filename = json[:backup][:filename]
|
37
|
+
|
38
|
+
# Dump
|
39
|
+
start_at = Time.now
|
40
|
+
tempfile = ruby19? ? Tempfile.new(filename, encoding: 'ascii-8bit') : Tempfile.new(filename)
|
41
|
+
log 'starting backup...'
|
42
|
+
log "tempfile = #{tempfile.path}"
|
43
|
+
log "command = #{@database.command}"
|
44
|
+
|
45
|
+
begin
|
46
|
+
pid, stdin, stdout, stderr = popen4(@database.command)
|
47
|
+
stdin.close
|
48
|
+
# Reuse buffer: http://www.ruby-forum.com/topic/134164
|
49
|
+
buffer_size = 1.megabytes
|
50
|
+
buffer = "\x00" * buffer_size # fixed-size malloc optimization
|
51
|
+
while stdout.read(buffer_size, buffer)
|
52
|
+
tempfile.write buffer
|
53
|
+
if tempfile.size > Backup::MAX_FILESIZE
|
54
|
+
raise 'Max filesize exceeded.'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
rescue
|
58
|
+
Process.kill(:INT, pid) rescue SystemCallError
|
59
|
+
@agent.send_request(api: 'backup/fail', params: { backup_id: backup_id, code: 'dump_error', message: $!.to_s })
|
60
|
+
abort
|
61
|
+
ensure
|
62
|
+
[stdin, stdout, stderr].each{|io| io.close unless io.closed? }
|
63
|
+
Process.waitpid(pid)
|
64
|
+
end
|
65
|
+
|
66
|
+
tempfile.flush
|
67
|
+
|
68
|
+
dump_duration = Time.now - start_at
|
69
|
+
log "dump_duration = #{dump_duration}"
|
70
|
+
|
71
|
+
upload_to_s3(json[:url], json[:fields], tempfile.path, filename)
|
72
|
+
|
73
|
+
json = @agent.send_request(api: 'backup/commit', params: { backup_id: backup_id, dump_duration: dump_duration.to_i })
|
74
|
+
rescue
|
75
|
+
log_last_error
|
76
|
+
ensure
|
77
|
+
tempfile.close(true)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Upload
|
81
|
+
def upload_to_s3(url, fields, local_file, remote_file)
|
82
|
+
require 'net/http/post/multipart'
|
83
|
+
fields['file'] = UploadIO.new(local_file, 'application/octet-stream', remote_file)
|
84
|
+
uri = URI.parse(url)
|
85
|
+
request = Net::HTTP::Post::Multipart.new uri.path, fields
|
86
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
87
|
+
if uri.is_a? URI::HTTPS
|
88
|
+
http.use_ssl = true
|
89
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
90
|
+
end
|
91
|
+
response = http.request(request)
|
92
|
+
log "response from S3 = #{response.to_s}"
|
93
|
+
response
|
94
|
+
rescue
|
95
|
+
log_last_error
|
96
|
+
end
|
97
|
+
|
98
|
+
# Helper
|
99
|
+
def ruby19?
|
100
|
+
RUBY_VERSION >= '1.9.0'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/dumper/stack.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module Dumper
|
2
|
+
class Stack
|
3
|
+
attr_accessor :rails_env, :dispatcher, :framework, :rackup, :activerecord_config
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
# Rackup?
|
7
|
+
@rackup = find_instance_in_object_space(Rack::Server)
|
8
|
+
|
9
|
+
# Rails?
|
10
|
+
if defined?(::Rails)
|
11
|
+
@framework = :rails
|
12
|
+
@rails_env = Rails.env.to_s
|
13
|
+
@rails_version = Rails::VERSION::STRING
|
14
|
+
@is_supported_rails_version = (::Rails::VERSION::MAJOR >= 3)
|
15
|
+
@activerecord_config = ActiveRecord::Base.configurations[@rails_env]
|
16
|
+
else
|
17
|
+
@framework = :ruby
|
18
|
+
end
|
19
|
+
|
20
|
+
# Which dispatcher?
|
21
|
+
[ :unicorn, :passenger, :thin, :mongrel, :webrick ].find do |name|
|
22
|
+
@dispatcher = send("#{name}?") ? name : nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_hash
|
27
|
+
{
|
28
|
+
framework: @framework,
|
29
|
+
rails_env: @rails_env,
|
30
|
+
rails_version: @rails_version,
|
31
|
+
dispatcher: @dispatcher,
|
32
|
+
activerecord_config: @activerecord_config.reject{|k,v| k == 'password' },
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Compatibility
|
37
|
+
def supported?
|
38
|
+
@is_supported_rails_version && @dispatcher && %w(mysql mysql2).include?(@activerecord_config['adapter'])
|
39
|
+
end
|
40
|
+
|
41
|
+
# Dispatcher
|
42
|
+
def unicorn?
|
43
|
+
defined?(::Unicorn::HttpServer) && find_instance_in_object_space(::Unicorn::HttpServer)
|
44
|
+
end
|
45
|
+
|
46
|
+
def passenger?
|
47
|
+
defined?(::Passenger::AbstractServer) || defined?(::IN_PHUSION_PASSENGER)
|
48
|
+
end
|
49
|
+
|
50
|
+
def thin?
|
51
|
+
# defined?(::Thin::Server) && find_instance_in_object_space(Thin::Server)
|
52
|
+
@rackup and @rackup.server.to_s.demodulize == 'Thin'
|
53
|
+
end
|
54
|
+
|
55
|
+
def mongrel?
|
56
|
+
# defined?(::Mongrel::HttpServer)
|
57
|
+
@rackup and @rackup.server.to_s.demodulize == 'Mongrel'
|
58
|
+
end
|
59
|
+
|
60
|
+
def webrick?
|
61
|
+
# defined?(::WEBrick::VERSION)
|
62
|
+
@rackup and @rackup.server.to_s.demodulize == 'WEBrick'
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_instance_in_object_space(klass)
|
66
|
+
ObjectSpace.each_object(klass).first
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/dumper/utility.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'ipaddr'
|
2
|
+
require 'logger'
|
2
3
|
|
3
4
|
module Dumper
|
4
5
|
module Utility
|
@@ -35,5 +36,37 @@ module Dumper
|
|
35
36
|
IPAddr.new("192.168.0.0/16") ].any?{|i| i.include? self }
|
36
37
|
end
|
37
38
|
end
|
39
|
+
|
40
|
+
class SlimLogger < ::Logger
|
41
|
+
def initialize(logdev, shift_age = 0, shift_size = 1048576)
|
42
|
+
super
|
43
|
+
self.formatter = SlimFormatter.new
|
44
|
+
self.formatter.datetime_format = "%Y-%m-%dT%H:%M:%S"
|
45
|
+
self.level = Logger::INFO
|
46
|
+
end
|
47
|
+
|
48
|
+
class SlimFormatter < ::Logger::Formatter
|
49
|
+
# This method is invoked when a log event occurs
|
50
|
+
def call(severity, time, progname, msg)
|
51
|
+
"[%s] %5s : %s\n" % [format_datetime(time), severity, msg2str(msg)]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module LoggingMethods
|
57
|
+
def logger
|
58
|
+
@logger ||= Dumper::Utility::SlimLogger.new("#{Rails.root}/log/dumper_agent.log", 1, 10.megabytes)
|
59
|
+
end
|
60
|
+
|
61
|
+
def log(msg, level=:info)
|
62
|
+
STDOUT.puts "** [Dumper] " + msg
|
63
|
+
return unless true #should_log?
|
64
|
+
logger.send level, msg
|
65
|
+
end
|
66
|
+
|
67
|
+
def log_last_error
|
68
|
+
log [ $!.class.name, $!.to_s ].join(', ')
|
69
|
+
end
|
70
|
+
end
|
38
71
|
end
|
39
72
|
end
|
data/lib/dumper/version.rb
CHANGED
data/spec/dumper_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dumper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,17 +9,49 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-05-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: multi_json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: posix-spawn
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.3.6
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.3.6
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
16
48
|
requirement: !ruby/object:Gem::Requirement
|
17
49
|
none: false
|
18
50
|
requirements:
|
19
51
|
- - ! '>='
|
20
52
|
- !ruby/object:Gem::Version
|
21
53
|
version: '0'
|
22
|
-
type: :
|
54
|
+
type: :development
|
23
55
|
prerelease: false
|
24
56
|
version_requirements: !ruby/object:Gem::Requirement
|
25
57
|
none: false
|
@@ -28,14 +60,14 @@ dependencies:
|
|
28
60
|
- !ruby/object:Gem::Version
|
29
61
|
version: '0'
|
30
62
|
- !ruby/object:Gem::Dependency
|
31
|
-
name:
|
63
|
+
name: rake
|
32
64
|
requirement: !ruby/object:Gem::Requirement
|
33
65
|
none: false
|
34
66
|
requirements:
|
35
67
|
- - ! '>='
|
36
68
|
- !ruby/object:Gem::Version
|
37
69
|
version: '0'
|
38
|
-
type: :
|
70
|
+
type: :development
|
39
71
|
prerelease: false
|
40
72
|
version_requirements: !ruby/object:Gem::Requirement
|
41
73
|
none: false
|
@@ -43,7 +75,8 @@ dependencies:
|
|
43
75
|
- - ! '>='
|
44
76
|
- !ruby/object:Gem::Version
|
45
77
|
version: '0'
|
46
|
-
description:
|
78
|
+
description: Dumper is a backup management system that offers a whole new way to take
|
79
|
+
daily backups of your databases. (coming soon!)
|
47
80
|
email:
|
48
81
|
- kenn.ejima@gmail.com
|
49
82
|
executables:
|
@@ -52,6 +85,7 @@ extensions: []
|
|
52
85
|
extra_rdoc_files: []
|
53
86
|
files:
|
54
87
|
- .gitignore
|
88
|
+
- .rspec
|
55
89
|
- Gemfile
|
56
90
|
- LICENSE
|
57
91
|
- README.md
|
@@ -59,10 +93,18 @@ files:
|
|
59
93
|
- bin/dumper
|
60
94
|
- dumper.gemspec
|
61
95
|
- lib/dumper.rb
|
96
|
+
- lib/dumper/agent.rb
|
62
97
|
- lib/dumper/cli.rb
|
98
|
+
- lib/dumper/database/base.rb
|
99
|
+
- lib/dumper/database/mysql.rb
|
100
|
+
- lib/dumper/dependency.rb
|
101
|
+
- lib/dumper/job.rb
|
102
|
+
- lib/dumper/stack.rb
|
63
103
|
- lib/dumper/utility.rb
|
64
104
|
- lib/dumper/version.rb
|
65
|
-
|
105
|
+
- spec/dumper_spec.rb
|
106
|
+
- spec/spec_helper.rb
|
107
|
+
homepage: https://github.com/kenn/dumper
|
66
108
|
licenses: []
|
67
109
|
post_install_message:
|
68
110
|
rdoc_options: []
|
@@ -85,6 +127,8 @@ rubyforge_project:
|
|
85
127
|
rubygems_version: 1.8.19
|
86
128
|
signing_key:
|
87
129
|
specification_version: 3
|
88
|
-
summary:
|
89
|
-
test_files:
|
130
|
+
summary: The Dumper Agent for Rails (coming soon!)
|
131
|
+
test_files:
|
132
|
+
- spec/dumper_spec.rb
|
133
|
+
- spec/spec_helper.rb
|
90
134
|
has_rdoc:
|