dumpdb 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.log
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ .rvmrc
8
+ .rbenv-version
9
+ Gemfile.lock
10
+ InstalledFiles
11
+ _yardoc
12
+ coverage
13
+ doc/
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ test/tmp
19
+ test/version_tmp
20
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'bundler', '~>1.1'
6
+ gem 'rake', '~>0.9.2'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012-Present Kelly Redding
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,263 @@
1
+ # Dumpdb
2
+
3
+ Dump and restore your databases.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'dumpdb'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install dumpdb
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ require 'dumpdb'
23
+
24
+ class MysqlFullRestore
25
+ include Dumpdb
26
+
27
+ databases { '/path/to/database.yml'}
28
+ dump_file { "dump.bz2" }
29
+ source { db('production', :output_root => '/some/source/dir') }
30
+ target { db('development', :output_root => '/some/target/dir') }
31
+
32
+ dump { "mysqldump -u :user -p\":pw\" :db | bzip2 > :dump_file" }
33
+ restore { "mysqladmin -u :user -p\":pw\" -f -b DROP :db; true" }
34
+ restore { "mysqladmin -u :user -p\":pw\" -f CREATE :db" }
35
+ restore { "bunzip2 -c :dump_file | mysql -u :user -p\":pw\" :db" }
36
+
37
+ end
38
+ ```
39
+
40
+ Dumpdb provides a framework for scripting database backups and restores. You configure your source and target db settings. You define the set of commands needed for your script to dump the (local or remote) source database and optionally restore the dump to the (local) target database.
41
+
42
+ ### Running
43
+
44
+ Once you have created an instance of your script with its database settings you can run it.
45
+
46
+ ```ruby
47
+ MysqlFullRestore.new.run
48
+ ```
49
+
50
+ Dumpdb runs the dump commands using source settings and runs the restore commands using target settings. By default, Dumpdb assumes both the dump and restore commands are to be run on the local system.
51
+
52
+ ### Runner Callbacks
53
+
54
+ Dumpdb supports defining callbacks for your script. These get fired as the script is being run.
55
+
56
+ ```ruby
57
+ class MysqlFullRestore
58
+ include Dumpdb
59
+
60
+ # ...
61
+
62
+ def after_dump
63
+ # this will be called after the dump commands have been run
64
+ end
65
+
66
+ end
67
+ ```
68
+
69
+ Available callbacks:
70
+
71
+ * `{before|after}_run` - called before/after any commands have been executed
72
+ * `{before|after}_setup` - called before/after the runner sets up the script run
73
+ * `{before|after}_dump` - called before/after the dump cmds are executed
74
+ * `{before|after}_copy_dump` - called before/after the dump file is copied from source to target
75
+ * `{before|after}_restore` - called before/after the restore cmds are executed
76
+ * `{before|after}_teardown` - called before/after the runner tears down the script run
77
+ * `{before|after}_cmd_run` - called before/after each cmd is run, passes the cmd obj being run
78
+
79
+ Phases occur in this order: setup, dump, copy_dump, restore, teardown
80
+
81
+ ### Remote dumps
82
+
83
+ To run your dump commands on a remote server, specify the optional `ssh` setting.
84
+
85
+ ```ruby
86
+ class MysqlFullRestore
87
+ include Dumpdb
88
+
89
+ ssh { 'user@host' }
90
+
91
+ # ...
92
+ end
93
+ ```
94
+
95
+ This tells Dumpdb to run the dump commands using ssh on a remote host and to download the dump file using sftp.
96
+
97
+ ## Define your script
98
+
99
+ Every Dumpdb script assumes there are two types of commands involved: dump commands that run using source settings and restore commands that run using target settings. The dump commands should produce a single "dump file" (typically a compressed file or tar). The restore commands restore the local db from the dump file.
100
+
101
+ ### The Dump File
102
+
103
+ You specify the name of the dump file using the `dump_file` setting
104
+
105
+ ```ruby
106
+ # ...
107
+ dump_file { "dump.bz2" }
108
+ #...
109
+ ```
110
+
111
+ This tells Dumpdb what file is being generated by the dump and will be used in the restore. The dump commands should produce it. The restore commands should use it.
112
+
113
+ ### Dump commands
114
+
115
+ Dump commands are system commands that should produce the dump file.
116
+
117
+ ```ruby
118
+ # ...
119
+ dump { "mysqldump -u :user -p :pw :db | bzip2 > :dump_file" }
120
+ #...
121
+ ```
122
+
123
+ ### Restore commands
124
+
125
+ Restore commands are system commands that should restore the local db from the dump file.
126
+
127
+ ```ruby
128
+ # ...
129
+ restore { "mysqladmin -u :user :pw -f -b DROP :db; true" } # drop the local db, whether it exists or not
130
+ restore { "mysqladmin -u :user :pw -f CREATE :db" } # recreate the local db
131
+ restore { "bunzip2 -c :dump_file | mysql -u :user :pw :db" } # unzip the dump file and apply it to the db
132
+ #...
133
+ ```
134
+
135
+ ### Command Placeholders
136
+
137
+ Dump and restore commands are templated. You define the command with placeholders and appropriate setting values are substituted in when the script is run.
138
+
139
+ Command placeholders should correspond with keys in the source or target settings. Dump commands use the source settings and restore commands use the target settings.
140
+
141
+ ### Special Placeholders
142
+
143
+ There are two special placeholders that are added to the source and target settings automatically:
144
+
145
+ * `:output_dir`
146
+ dir the dump file is written to or read from (depending on whether dumping or restoring). This is generated by the script instance. By default, no specific root value is used - pass in a `:output_root` value to the source and target to specify one.
147
+
148
+ * `:dump_file`
149
+ path of the dump file - uses the :output_dir setting
150
+
151
+ You should at least use the `:dump_file` placeholder in your dump and restore commands to ensure proper dump handling and usage.
152
+
153
+ ```ruby
154
+ dump_file { "dump.bz2" }
155
+
156
+ dump { "mysqldump :db | bzip2 > :dump_file" }
157
+ restore { "bunzip2 -c :dump_file | mysql :db" }
158
+ ```
159
+
160
+ ## Source / Target settings
161
+
162
+ A Dumpdb script needs to be told about its source and target settings. You tell it these when you define your script:
163
+
164
+ ```ruby
165
+ class MysqlFullRestore
166
+ include Dumpdb
167
+
168
+ source do
169
+ { 'user' => 'something',
170
+ 'pw' => 'secret',
171
+ 'db' => 'something_production',
172
+ 'something' => 'else'
173
+ }
174
+ end
175
+
176
+ target do
177
+ { 'user' => 'root',
178
+ 'pw' => 'supersecret',
179
+ 'db' => 'something_development'
180
+ }
181
+ end
182
+
183
+ # ...
184
+ end
185
+ ```
186
+
187
+ Any settings keys can be used as command placeholders in dump and restore commands.
188
+
189
+ **Note:** When reading source and target settings, Dumpdb takes common keys like 'hostname', 'username', 'password', and 'database' and exposes them with the more succinct 'host', 'user', 'pw', and 'db'.
190
+
191
+ ### Lookup settings from YAML
192
+
193
+ Since many ORMs allow you to configure db connections using yaml files, Dumpdb supports specifying your databases from a yaml file.
194
+
195
+ ```ruby
196
+ class MysqlFullRestore
197
+ include Dumpdb
198
+
199
+ databases { '/path/to/database.yml' }
200
+
201
+ # ...
202
+ end
203
+ ```
204
+
205
+ Now you can lookup your source and target settings using the `db` method.
206
+
207
+ ```ruby
208
+ databases { '/path/to/database.yml' }
209
+ source { db('production') }
210
+ target { db('development') }
211
+ ```
212
+
213
+ You can merge in additional settings by passing them to the `db` command:
214
+
215
+ ```ruby
216
+ class MysqlFullRestore
217
+ include Dumpdb
218
+
219
+ databases { '/path/to/database.yml' }
220
+ source { db('produciton', :something => 'else') }
221
+
222
+ # ...
223
+ end
224
+ ```
225
+
226
+ ### Building Commands
227
+
228
+ As you may have noticed, the script DSL settings methods all take a proc as their argument. This is because the procs are lazy-eval'd in the scope of the script instance. This allows you to use interpolation to help build commands with dynamic data.
229
+
230
+ Take this example where you want your dump script to honor ignored tables.
231
+
232
+ ```ruby
233
+ require 'dumpdb'
234
+
235
+ class MysqlIgnoredTablesRestore
236
+ include Dumpdb
237
+
238
+ # ...
239
+ dump { "mysqldump -u :user -p :pw :db #{ignored_tables} | bzip2 > :dump_file" }
240
+ # ...
241
+
242
+ def initialize(opts={})
243
+ opts[:ignored_tables] ||= []
244
+ @opts = opts
245
+ end
246
+
247
+ def ignored_tables
248
+ @opts[:ignored_tables].collect {|t| "--ignore-table=#{source.db}.#{t}"}.join(' ')
249
+ end
250
+ end
251
+ ```
252
+
253
+ ## Examples
254
+
255
+ See `examples/` dir. (TODO)
256
+
257
+ ## Contributing
258
+
259
+ 1. Fork it
260
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
261
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
262
+ 4. Push to the branch (`git push origin my-new-feature`)
263
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'assert/rake_tasks'
4
+ Assert::RakeTasks.install
5
+
6
+ require 'bundler/gem_tasks'
7
+
8
+ task :default => :build
data/dumpdb.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/dumpdb/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "dumpdb"
6
+ gem.version = Dumpdb::VERSION
7
+ gem.description = %q{Dump and restore your databases.}
8
+ gem.summary = %q{Dump and restore your databases.}
9
+
10
+ gem.authors = ["Kelly Redding", "Collin Redding"]
11
+ gem.email = ["kelly@kellyredding.com", "collin.redding@me.com"]
12
+ gem.homepage = "http://github.com/redding/dumpdb"
13
+
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_development_dependency("assert")
20
+ gem.add_dependency("scmd", ["~>2.0"])
21
+ gem.add_dependency("ns-options", ["1.0.0.rc1"])
22
+ end
data/lib/dumpdb/db.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'ostruct'
2
+
3
+ module Dumpdb
4
+
5
+ class Db
6
+
7
+ def initialize(dump_file_name, values=nil)
8
+ @dump_file_name = dump_file_name || 'dump.output'
9
+ @values = (values || {}).inject({}) do |vals, (k, v)|
10
+ # stringify keys
11
+ vals.merge(k.to_s => v)
12
+ end
13
+
14
+ @values['host'] ||= (@values['hostname'] || '')
15
+ @values['user'] ||= (@values['username'] || '')
16
+ @values['pw'] ||= (@values['password'] || '')
17
+ @values['db'] ||= (@values['database'] || '')
18
+ @values['output_root'] ||= ''
19
+
20
+ @values['output_dir'] = output_dir(self.output_root, "#{self.host}__#{self.db}")
21
+ @values['dump_file'] = File.join(self.output_dir, @dump_file_name)
22
+ end
23
+
24
+ def to_hash; @values; end
25
+
26
+ def method_missing(meth, *args, &block)
27
+ if @values.has_key?(meth.to_s)
28
+ @values[meth.to_s]
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def respond_to?(meth)
35
+ @values.has_key?(meth.to_s) || super
36
+ end
37
+
38
+ private
39
+
40
+ def output_dir(root, name)
41
+ name_time = "#{name}__#{Time.now.to_f}"
42
+ root && !root.empty? ? File.join(root, name_time) : name_time
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,76 @@
1
+ require 'dumpdb/settings'
2
+
3
+ module Dumpdb
4
+ class Runner
5
+
6
+ attr_reader :script, :cmd_runner
7
+
8
+ def initialize(script, opts={})
9
+ @script = script
10
+ @cmd_runner = opts[:cmd_runner] || scmd_cmd_runner
11
+ end
12
+
13
+ def run
14
+ run_callback 'before_run'
15
+ run_callback 'before_setup'
16
+ run_setup
17
+
18
+ begin
19
+ run_callback 'after_setup'
20
+ [:dump, :copy_dump, :restore].each{|phase_name| run_phase phase_name}
21
+ ensure
22
+ run_phase 'teardown'
23
+ run_callback 'after_run'
24
+ end
25
+ end
26
+
27
+ protected
28
+
29
+ def run_setup
30
+ run_cmd(@script.dump_cmd { "mkdir -p #{source.output_dir}" })
31
+ run_cmd(@script.restore_cmd { "mkdir -p #{target.output_dir}" })
32
+ end
33
+
34
+ def run_dump
35
+ @script.dump_cmds.each{|cmd| run_cmd(cmd)}
36
+ end
37
+
38
+ def run_copy_dump
39
+ run_cmd @script.copy_dump_cmd
40
+ end
41
+
42
+ def run_restore
43
+ @script.restore_cmds.each{|cmd| run_cmd(cmd)}
44
+ end
45
+
46
+ def run_teardown
47
+ run_cmd(@script.dump_cmd { "rm -rf #{source.output_dir}" })
48
+ run_cmd(@script.restore_cmd { "rm -rf #{target.output_dir}" })
49
+ end
50
+
51
+ private
52
+
53
+ def run_phase(phase_name)
54
+ run_callback "before_#{phase_name}"
55
+ self.send("run_#{phase_name}")
56
+ run_callback "after_#{phase_name}"
57
+ end
58
+
59
+ def run_cmd(cmd_str)
60
+ cmd_obj = @cmd_runner.new(cmd_str)
61
+ run_callback('before_cmd_run', cmd_obj)
62
+ cmd_obj.run!
63
+ run_callback('after_cmd_run', cmd_obj)
64
+ end
65
+
66
+ def run_callback(meth, *args)
67
+ @script.send(meth.to_s, *args)
68
+ end
69
+
70
+ def scmd_cmd_runner
71
+ require 'scmd'
72
+ Scmd
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,100 @@
1
+ require 'dumpdb/db'
2
+
3
+ module Dumpdb::Settings
4
+
5
+ class Base
6
+
7
+ attr_reader :proc
8
+
9
+ def initialize(proc=nil)
10
+ @proc = proc.kind_of?(::Proc) ? proc : Proc.new { proc }
11
+ end
12
+
13
+ def value(script)
14
+ script.instance_eval &@proc
15
+ end
16
+
17
+ end
18
+
19
+ class Ssh < Base; end
20
+
21
+ class Databases < Base
22
+
23
+ def value(script)
24
+ val = super
25
+ val.kind_of?(::String) ? load_yaml(val) : val
26
+ end
27
+
28
+ private
29
+
30
+ def load_yaml(file_path)
31
+ YAML.load(File.read(File.expand_path(file_path)))
32
+ end
33
+ end
34
+
35
+ class DumpFile < Base; end
36
+
37
+ class SourceTarget < Base
38
+
39
+ def value(script)
40
+ val = super
41
+ val.kind_of?(Dumpdb::Db) ? val : Dumpdb::Db.new(script.dump_file, val)
42
+ end
43
+
44
+ end
45
+
46
+ class Cmd < Base
47
+
48
+ def value(script, placeholder_vals)
49
+ hsub(super(script), placeholder_vals)
50
+ end
51
+
52
+ private
53
+
54
+ def hsub(string, hash)
55
+ hash.inject(string) {|new_str, (k, v)| new_str.gsub(":#{k}", v.to_s)}
56
+ end
57
+
58
+ end
59
+
60
+ class DumpCmd < Cmd
61
+
62
+ def value(script, placeholder_vals={})
63
+ val = super(script, script.source.to_hash.merge(placeholder_vals))
64
+ if script.ssh?
65
+ val = "ssh -A #{script.ssh_opts} #{script.ssh} \"#{val}\""
66
+ end
67
+ val
68
+ end
69
+
70
+ end
71
+
72
+ class RestoreCmd < Cmd
73
+
74
+ def value(script, placeholder_vals={})
75
+ super(script, script.target.to_hash.merge(placeholder_vals))
76
+ end
77
+
78
+ end
79
+
80
+ class CopyDumpCmd < Cmd
81
+
82
+ def value(script)
83
+ if script.ssh?
84
+ "sftp #{script.ssh_opts} #{script.ssh}:#{script.source.dump_file} #{script.target.dump_file}"
85
+ else
86
+ "cp #{script.source.dump_file} #{script.target.dump_file}"
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ class CmdList < ::Array
93
+
94
+ def value(script, placeholder_vals={})
95
+ self.map{|cmd| cmd.value(script, placeholder_vals)}
96
+ end
97
+
98
+ end
99
+
100
+ end
@@ -0,0 +1,3 @@
1
+ module Dumpdb
2
+ VERSION = "1.0.0.rc1"
3
+ end
data/lib/dumpdb.rb ADDED
@@ -0,0 +1,102 @@
1
+ require 'ns-options'
2
+
3
+ require 'dumpdb/settings'
4
+ require 'dumpdb/db'
5
+ require 'dumpdb/runner'
6
+
7
+ module Dumpdb
8
+
9
+ class BadDatabaseName < RuntimeError; end
10
+
11
+ def self.included(receiver)
12
+ receiver.class_eval do
13
+ include NsOptions
14
+ options :settings do
15
+ option 'ssh', Settings::Ssh, :default => ''
16
+ option 'databases', Settings::Databases, :default => {}
17
+ option 'dump_file', Settings::DumpFile, :default => ''
18
+ option 'source', Settings::SourceTarget, :default => {}
19
+ option 'target', Settings::SourceTarget, :default => {}
20
+ option 'dump_cmds', Settings::CmdList, :default => []
21
+ option 'restore_cmds', Settings::CmdList, :default => []
22
+ end
23
+
24
+ extend SettingsDslMethods
25
+ include SettingsMethods
26
+
27
+ def self.inherited(subclass)
28
+ subclass.settings.apply(self.settings.to_hash)
29
+ end
30
+
31
+ end
32
+ end
33
+
34
+ module SettingsDslMethods
35
+
36
+ def ssh(&block); settings.ssh = Settings::Ssh.new(block); end
37
+ def databases(&block); settings.databases = Settings::Databases.new(block); end
38
+ def dump_file(&block); settings.dump_file = Settings::DumpFile.new(block); end
39
+ def source(&block); settings.source = Settings::SourceTarget.new(block); end
40
+ def target(&block); settings.target = Settings::SourceTarget.new(block); end
41
+
42
+ def dump(&block); settings.dump_cmds << Settings::DumpCmd.new(block); end
43
+ def restore(&block); settings.restore_cmds << Settings::RestoreCmd.new(block); end
44
+
45
+ end
46
+
47
+ module SettingsMethods
48
+
49
+ def settings; self.class.settings; end
50
+
51
+ def ssh; @ssh ||= settings.ssh.value(self); end
52
+ def databases; @databases ||= settings.databases.value(self); end
53
+ def dump_file; @dump_file ||= settings.dump_file.value(self); end
54
+ def source; @source ||= settings.source.value(self); end
55
+ def target; @target ||= settings.target.value(self); end
56
+
57
+ def dump_cmds; @dump_cmds ||= settings.dump_cmds.value(self); end
58
+ def restore_cmds; @restore_cmds ||= settings.restore_cmds.value(self); end
59
+ def copy_dump_cmd; @copy_dump_cmd ||= Settings::CopyDumpCmd.new.value(self); end
60
+
61
+ end
62
+
63
+ def db(database_name, other_vals=nil)
64
+ if (db_vals = self.databases[database_name]).nil?
65
+ raise BadDatabaseName, "no database named `#{database_name}'."
66
+ end
67
+ Db.new(self.dump_file, db_vals.merge(other_vals || {}))
68
+ end
69
+
70
+ def dump_cmd(&block); Settings::DumpCmd.new(block).value(self); end
71
+ def restore_cmd(&block) Settings::RestoreCmd.new(block).value(self); end
72
+
73
+ def ssh?
74
+ self.ssh && !self.ssh.empty?
75
+ end
76
+
77
+ def ssh_opts
78
+ "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10"
79
+ end
80
+
81
+ def run(cmd_runner=nil)
82
+ Runner.new(self, :cmd_runner => cmd_runner).run
83
+ end
84
+
85
+ # Callbacks
86
+
87
+ def before_run(*args); end
88
+ def after_run(*args); end
89
+ def before_setup(*args); end
90
+ def after_setup(*args); end
91
+ def before_dump(*args); end
92
+ def after_dump(*args); end
93
+ def before_copy_dump(*args); end
94
+ def after_copy_dump(*args); end
95
+ def before_restore(*args); end
96
+ def after_restore(*args); end
97
+ def before_teardown(*args); end
98
+ def after_teardown(*args); end
99
+ def before_cmd_run(*args); end
100
+ def after_cmd_run(*args); end
101
+
102
+ end
data/test/db_tests.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'assert'
2
+ require 'ostruct'
3
+
4
+ module Dumpdb
5
+
6
+ class DbTests < Assert::Context
7
+ desc "the Db helper class"
8
+ setup do
9
+ @db = Db.new(nil)#(:host => 'h', :user => 'u', :pw => 'p', :db => 'd')
10
+ end
11
+ subject { @db }
12
+
13
+ should have_imeths :host, :user, :pw, :db, :output_root, :output_dir, :dump_file
14
+
15
+ should "default its values" do
16
+ [:host, :user, :pw, :db, :output_root].each do |val|
17
+ assert_equal '', subject.send(val)
18
+ end
19
+ assert_match '____', subject.output_dir
20
+ assert_match 'dump.output', subject.dump_file
21
+ end
22
+
23
+ should "set values" do
24
+ db = Db.new('dump.file', :host => 'h', :db => 'd', :something => 'else')
25
+
26
+ assert_equal 'h', db.host
27
+ assert_equal 'd', db.db
28
+ assert_match 'h__d__', db.output_dir
29
+ assert_match 'dump.file', db.dump_file
30
+ end
31
+ end
32
+
33
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,7 @@
1
+ # this file is automatically required in when you require 'assert' in your tests
2
+ # put test helpers here
3
+
4
+ # add root dir to the load path
5
+ $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
6
+
7
+ require 'test/support/test_scripts'
data/test/irb.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'assert/setup'
2
+
3
+ # this file is required in when the 'irb' rake test is run.
4
+ # b/c 'assert/setup' is required above, the test helper will be
5
+ # required in as well.
6
+
7
+ # put any IRB setup code here
8
+
9
+ require 'dumpdb'
@@ -0,0 +1,41 @@
1
+ require 'assert'
2
+ require 'test/support/fake_cmd_runner'
3
+
4
+ require 'dumpdb/runner'
5
+
6
+ module Dumpdb
7
+
8
+ class RunnerTests < Assert::Context
9
+ desc "the runner"
10
+ setup do
11
+ FakeCmdRunner.reset
12
+ @script = RunnerScript.new
13
+ @runner = Runner.new(@script, :cmd_runner => FakeCmdRunner)
14
+ end
15
+ teardown do
16
+ FakeCmdRunner.reset
17
+ end
18
+ subject { @runner }
19
+
20
+
21
+ should have_reader :script, :cmd_runner
22
+
23
+ should "run the script" do
24
+ assert_empty FakeCmdRunner.cmds
25
+ subject.run
26
+
27
+ assert_not_empty FakeCmdRunner.cmds
28
+ assert_equal 7, FakeCmdRunner.cmds.size
29
+ assert_equal "a restore cmd", FakeCmdRunner.cmds[-3]
30
+ end
31
+
32
+ should "call the callbacks" do
33
+ assert_not @script.all_callbacks_called?
34
+ subject.run
35
+
36
+ assert @script.all_callbacks_called?
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,130 @@
1
+ require 'assert'
2
+
3
+ module Dumpdb
4
+
5
+ class ScriptTests < Assert::Context
6
+ desc "the main script mixin"
7
+ setup do
8
+ @script = LocalScript.new
9
+ end
10
+ subject { @script }
11
+
12
+ should have_cmeths :settings
13
+ should have_imeths :settings, :db, :dump_cmd, :restore_cmd, :ssh?, :ssh_opts, :run
14
+
15
+ should have_cmeths :ssh, :databases, :dump_file, :source, :target
16
+ should have_imeths :ssh, :databases, :dump_file, :source, :target
17
+
18
+ should have_cmeths :dump, :restore
19
+ should have_imeths :dump_cmds, :restore_cmds, :copy_dump_cmd
20
+
21
+ should have_imeths :before_run, :before_setup, :before_teardown
22
+ should have_imeths :after_run, :after_setup, :after_teardown
23
+ should have_imeths :before_dump, :before_copy_dump, :before_restore
24
+ should have_imeths :after_dump, :after_copy_dump, :after_restore
25
+
26
+ should "store its settings using ns-options" do
27
+ assert_kind_of NsOptions::Namespace, subject.class.settings
28
+ assert_same subject.class.settings, subject.settings
29
+ end
30
+
31
+ should "store off the settings for the script" do
32
+ assert_kind_of Settings::Ssh, subject.settings.ssh
33
+ assert_kind_of Settings::Databases, subject.settings.databases
34
+ assert_kind_of Settings::DumpFile, subject.settings.dump_file
35
+ assert_kind_of Settings::SourceTarget, subject.settings.source
36
+ assert_kind_of Settings::SourceTarget, subject.settings.target
37
+ assert_kind_of Settings::CmdList, subject.settings.dump_cmds
38
+ assert_kind_of Settings::CmdList, subject.settings.restore_cmds
39
+ end
40
+
41
+ end
42
+
43
+ class DbMethTests < ScriptTests
44
+ desc "`db' method"
45
+ setup do
46
+ @script = LocalScript.new
47
+ end
48
+
49
+ should "build a Db based on the named database values" do
50
+ assert_kind_of Db, subject.target
51
+ assert_equal 'testhost', subject.target.host
52
+ end
53
+
54
+ should "build a Db based on the named database values plus additional values" do
55
+ assert_kind_of Db, subject.source
56
+ assert_equal 'devhost', subject.source.host
57
+ assert_equal 'value', subject.source.another
58
+ end
59
+
60
+ should "complain if looking up a db not in the `databases` collection" do
61
+ assert_raises BadDatabaseName do
62
+ subject.db('does_not_exist')
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ class CmdMethsTests < ScriptTests
69
+
70
+ should "build dump command strings" do
71
+ assert_equal 'echo local', subject.dump_cmd { "echo #{type}" }
72
+ end
73
+
74
+ should "build restore command strings" do
75
+ assert_equal 'echo local', subject.restore_cmd { "echo #{type}" }
76
+ end
77
+
78
+ end
79
+
80
+ class SshTests < ScriptTests
81
+
82
+ should "know if its in ssh mode or not" do
83
+ assert RemoteScript.new.ssh?
84
+ assert_not LocalScript.new.ssh?
85
+ end
86
+
87
+ should "know what ssh options to use" do
88
+ exp_opts = "-o UserKnownHostsFile=/dev/null"\
89
+ " -o StrictHostKeyChecking=no"\
90
+ " -o ConnectTimeout=10"\
91
+
92
+ assert_equal exp_opts, subject.ssh_opts
93
+ end
94
+
95
+ end
96
+
97
+ class RunTests < ScriptTests
98
+ setup do
99
+ FakeCmdRunner.reset
100
+ @script = RunnerScript.new
101
+ end
102
+ teardown do
103
+ FakeCmdRunner.reset
104
+ end
105
+
106
+ should "run the script when `run` is called" do
107
+ assert_empty FakeCmdRunner.cmds
108
+ @script.run(FakeCmdRunner)
109
+
110
+ assert_not_empty FakeCmdRunner.cmds
111
+ assert_equal 7, FakeCmdRunner.cmds.size
112
+ assert_equal "a restore cmd", FakeCmdRunner.cmds[-3]
113
+ end
114
+
115
+ end
116
+
117
+ class InheritedTests < ScriptTests
118
+ desc "when inherited"
119
+ setup do
120
+ @a_remote_script = RemoteScript.new
121
+ @a_sub_remote_script = Class.new(RemoteScript).new
122
+ end
123
+
124
+ should "pass its definition values to any subclass" do
125
+ assert_equal @a_remote_script.ssh, @a_sub_remote_script.ssh
126
+ end
127
+
128
+ end
129
+
130
+ end
@@ -0,0 +1,200 @@
1
+ require 'assert'
2
+
3
+ require 'dumpdb/settings'
4
+
5
+ module Dumpdb
6
+
7
+ class SettingsTests < Assert::Context
8
+ desc "the script settings"
9
+ setup do
10
+ @setting = Settings::Base.new
11
+ @script = LocalScript.new
12
+ end
13
+ subject { @setting }
14
+
15
+ should have_imeth :value
16
+ should have_reader :proc
17
+
18
+ should "know its value proc" do
19
+ assert_kind_of ::Proc, subject.proc
20
+ assert_nil subject.proc.call
21
+ end
22
+
23
+ should "instance eval its proc in the scope of a script to return a value" do
24
+ setting = Settings::Base.new(Proc.new { "something: #{type}"})
25
+
26
+ assert_equal "local", @script.type
27
+ assert_equal "something: local", setting.value(@script)
28
+ end
29
+
30
+ end
31
+
32
+ class SshSettingTests < SettingsTests
33
+ desc "`ssh` setting"
34
+
35
+ should "be available" do
36
+ assert Settings::Ssh
37
+ end
38
+
39
+ end
40
+
41
+ class DatabasesSettingTests < SettingsTests
42
+ desc "`databases` setting"
43
+ setup do
44
+ @from_hash = {
45
+ 'db1' => {'db' => 'one'},
46
+ 'db2' => {'db' => 'two'},
47
+ }
48
+ end
49
+
50
+ should "be available" do
51
+ assert Settings::Databases
52
+ end
53
+
54
+ should "come from a hash" do
55
+ databases = Settings::Databases.new(@from_hash)
56
+ assert_equal @from_hash, databases.value(@script)
57
+ end
58
+
59
+ should "come from a yaml file" do
60
+ databases = Settings::Databases.new('test/support/test.yaml')
61
+ assert_equal({
62
+ 'db-one' => {'db' => 1},
63
+ 'db-two' => {'db' => 2},
64
+ }, databases.value(@script))
65
+ end
66
+
67
+ end
68
+
69
+ class DumpFileSettingTests < SettingsTests
70
+ desc "`dump_file` setting"
71
+
72
+ should "be available" do
73
+ assert Settings::DumpFile
74
+ end
75
+
76
+ end
77
+
78
+ class SourceTargetSettingTests < SettingsTests
79
+ desc "`source` or `target` setting"
80
+ setup do
81
+ @from_hash = {'host' => 'from_hash'}
82
+ end
83
+
84
+ should "be available" do
85
+ assert Settings::SourceTarget
86
+ end
87
+
88
+ should "come from a hash" do
89
+ db = Settings::SourceTarget.new(@from_hash).value(@script)
90
+
91
+ assert_kind_of Db, db
92
+ assert_equal 'from_hash', db.host
93
+ end
94
+
95
+ should "come from a Db obj" do
96
+ db = Settings::Databases.new(Db.new('dump.file', {'host' => 'from_db'})).value(@script)
97
+
98
+ assert_kind_of Db, db
99
+ assert_equal 'from_db', db.host
100
+ end
101
+
102
+ end
103
+
104
+ class CmdTests < SettingsTests
105
+ desc "command helper class"
106
+ setup do
107
+ @cmd_str = Proc.new { "this is the #{type} db: :db" }
108
+ end
109
+
110
+ should "be available" do
111
+ assert Settings::Cmd
112
+ end
113
+
114
+ should "eval and apply any placeholders to the cmd string" do
115
+ cmd_val = Settings::Cmd.new(@cmd_str).value(@script, @script.source.to_hash)
116
+ assert_equal "this is the local db: devdb", cmd_val
117
+ end
118
+
119
+ end
120
+
121
+ class DumpCmdTests < CmdTests
122
+ desc "for dump commands"
123
+
124
+ should "eval and apply any source placeholders to the cmd string" do
125
+ cmd_val = Settings::DumpCmd.new(@cmd_str).value(@script)
126
+ assert_equal "this is the local db: devdb", cmd_val
127
+ end
128
+
129
+ end
130
+
131
+ class RemoteDumpCmdTests < DumpCmdTests
132
+ desc "using ssh"
133
+ setup do
134
+ @script = RemoteScript.new
135
+ @cmd_str = Proc.new { "echo hello" }
136
+ end
137
+
138
+ should "build the cmds to run remtoely using ssh" do
139
+ exp_cmd_str = "ssh -A #{@script.ssh_opts} #{@script.ssh} \"echo hello\""
140
+ cmd_val = Settings::DumpCmd.new(@cmd_str).value(@script)
141
+
142
+ assert_equal exp_cmd_str, cmd_val
143
+ end
144
+
145
+ end
146
+
147
+ class RestoreCmdTests < CmdTests
148
+ desc "for restore commands"
149
+
150
+ should "eval and apply any target placeholders to the cmd string" do
151
+ cmd_val = Settings::RestoreCmd.new(@cmd_str).value(@script)
152
+ assert_equal "this is the local db: testdb", cmd_val
153
+ end
154
+
155
+ end
156
+
157
+ class CopyDumpCmdTests < CmdTests
158
+
159
+ should "be a copy cmd for non-ssh scripts" do
160
+ script = @script
161
+ exp_cmd = "cp #{script.source.dump_file} #{script.target.dump_file}"
162
+
163
+ assert_equal exp_cmd, script.copy_dump_cmd
164
+ end
165
+
166
+ should "be an sftp cmd for ssh scripts" do
167
+ script = RemoteScript.new
168
+ exp_cmd = "sftp #{script.ssh_opts} #{script.ssh}:#{script.source.dump_file} #{script.target.dump_file}"
169
+
170
+ assert_equal exp_cmd, script.copy_dump_cmd
171
+ end
172
+
173
+ end
174
+
175
+ class CmdListTests < SettingsTests
176
+ desc "command list helper class"
177
+ setup do
178
+ @cmds = [
179
+ Settings::Cmd.new(Proc.new { "this is the #{type} target db: :db" }),
180
+ Settings::Cmd.new(Proc.new { "this is the #{type} target host: :host" })
181
+ ]
182
+ @exp_val_cmds = [
183
+ "this is the local target db: testdb",
184
+ "this is the local target host: testhost"
185
+ ]
186
+ end
187
+
188
+ should "be an Array" do
189
+ assert_kind_of ::Array, Settings::CmdList.new
190
+ end
191
+
192
+ should "return the commands, eval'd and placeholders applied" do
193
+ val_cmds = Settings::CmdList.new(@cmds).value(@script, @script.target.to_hash)
194
+ assert_equal @exp_val_cmds, val_cmds
195
+ end
196
+
197
+ end
198
+
199
+
200
+ end
@@ -0,0 +1,12 @@
1
+ ---
2
+ test:
3
+ hostname: testhost
4
+ username: testuser
5
+ password: testpw
6
+ database: testdb
7
+
8
+ development:
9
+ hostname: devhost
10
+ username: devuser
11
+ password: devpw
12
+ database: devdb
@@ -0,0 +1,28 @@
1
+ module Dumpdb
2
+
3
+ class FakeCmdRunner
4
+
5
+ def self.cmds
6
+ @@cmds ||= []
7
+ @@cmds
8
+ end
9
+
10
+ def self.reset
11
+ @@cmds = []
12
+ end
13
+
14
+ def initialize(cmd)
15
+ @cmd = cmd
16
+ end
17
+
18
+ def run!
19
+ run
20
+ end
21
+
22
+ def run
23
+ FakeCmdRunner.cmds << @cmd
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,6 @@
1
+ ---
2
+ "db-one":
3
+ db: 1
4
+
5
+ "db-two":
6
+ db: 2
@@ -0,0 +1,66 @@
1
+ require 'dumpdb'
2
+
3
+ class LocalScript
4
+ include Dumpdb
5
+
6
+ databases { 'test/support/database.yaml' }
7
+ dump_file { "dump.#{type}" }
8
+ source { db('development', :another => 'value') }
9
+ target { db('test') }
10
+
11
+ def type; "local"; end
12
+ end
13
+
14
+ class RemoteScript
15
+ include Dumpdb
16
+
17
+ ssh { 'user@example.com' }
18
+ databases { 'test/support/database.yaml' }
19
+ dump_file { "dump.#{type}" }
20
+ source { db('development') }
21
+ target { db('test') }
22
+
23
+ def type; "remote"; end
24
+ end
25
+
26
+ class RunnerScript
27
+ include Dumpdb
28
+
29
+ dump_file { 'dump.output' }
30
+
31
+ dump { 'a dump cmd' }
32
+ restore { 'a restore cmd' }
33
+
34
+ def initialize
35
+ @before_cmd_run = 0
36
+ @after_cmd_run = 0
37
+ end
38
+
39
+ def all_callbacks_called?
40
+ !!(@before_run && @after_run &&
41
+ @before_setup && @after_setup &&
42
+ @before_dump && @after_dump &&
43
+ @before_copy_dump && @after_copy_dump &&
44
+ @before_restore && @after_restore &&
45
+ @before_teardown && @after_teardown &&
46
+ @before_cmd_run == 7 &&
47
+ @after_cmd_run == 7
48
+ )
49
+ end
50
+
51
+ def before_run(*args); @before_run = true; end
52
+ def after_run(*args); @after_run = true; end
53
+ def before_setup(*args); @before_setup = true; end
54
+ def after_setup(*args); @after_setup = true; end
55
+ def before_dump(*args); @before_dump = true; end
56
+ def after_dump(*args); @after_dump = true; end
57
+ def before_copy_dump(*args); @before_copy_dump = true; end
58
+ def after_copy_dump(*args); @after_copy_dump = true; end
59
+ def before_restore(*args); @before_restore = true; end
60
+ def after_restore(*args); @after_restore = true; end
61
+ def before_teardown(*args); @before_teardown = true; end
62
+ def after_teardown(*args); @after_teardown = true; end
63
+ def before_cmd_run(*args); @before_cmd_run += 1; end
64
+ def after_cmd_run(*args); @after_cmd_run += 1; end
65
+
66
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dumpdb
3
+ version: !ruby/object:Gem::Version
4
+ hash: 3130626483
5
+ prerelease: 6
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ - rc
11
+ - 1
12
+ version: 1.0.0.rc1
13
+ platform: ruby
14
+ authors:
15
+ - Kelly Redding
16
+ - Collin Redding
17
+ autorequire:
18
+ bindir: bin
19
+ cert_chain: []
20
+
21
+ date: 2012-11-21 00:00:00 Z
22
+ dependencies:
23
+ - !ruby/object:Gem::Dependency
24
+ name: assert
25
+ version_requirements: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :development
35
+ requirement: *id001
36
+ prerelease: false
37
+ - !ruby/object:Gem::Dependency
38
+ name: scmd
39
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 2
47
+ - 0
48
+ version: "2.0"
49
+ type: :runtime
50
+ requirement: *id002
51
+ prerelease: false
52
+ - !ruby/object:Gem::Dependency
53
+ name: ns-options
54
+ version_requirements: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - "="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3130626483
60
+ segments:
61
+ - 1
62
+ - 0
63
+ - 0
64
+ - rc
65
+ - 1
66
+ version: 1.0.0.rc1
67
+ type: :runtime
68
+ requirement: *id003
69
+ prerelease: false
70
+ description: Dump and restore your databases.
71
+ email:
72
+ - kelly@kellyredding.com
73
+ - collin.redding@me.com
74
+ executables: []
75
+
76
+ extensions: []
77
+
78
+ extra_rdoc_files: []
79
+
80
+ files:
81
+ - .gitignore
82
+ - Gemfile
83
+ - LICENSE
84
+ - README.md
85
+ - Rakefile
86
+ - dumpdb.gemspec
87
+ - lib/dumpdb.rb
88
+ - lib/dumpdb/db.rb
89
+ - lib/dumpdb/runner.rb
90
+ - lib/dumpdb/settings.rb
91
+ - lib/dumpdb/version.rb
92
+ - test/db_tests.rb
93
+ - test/helper.rb
94
+ - test/irb.rb
95
+ - test/runner_tests.rb
96
+ - test/script_tests.rb
97
+ - test/settings_tests.rb
98
+ - test/support/database.yaml
99
+ - test/support/fake_cmd_runner.rb
100
+ - test/support/test.yaml
101
+ - test/support/test_scripts.rb
102
+ homepage: http://github.com/redding/dumpdb
103
+ licenses: []
104
+
105
+ post_install_message:
106
+ rdoc_options: []
107
+
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ hash: 3
116
+ segments:
117
+ - 0
118
+ version: "0"
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">"
123
+ - !ruby/object:Gem::Version
124
+ hash: 25
125
+ segments:
126
+ - 1
127
+ - 3
128
+ - 1
129
+ version: 1.3.1
130
+ requirements: []
131
+
132
+ rubyforge_project:
133
+ rubygems_version: 1.8.24
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Dump and restore your databases.
137
+ test_files:
138
+ - test/db_tests.rb
139
+ - test/helper.rb
140
+ - test/irb.rb
141
+ - test/runner_tests.rb
142
+ - test/script_tests.rb
143
+ - test/settings_tests.rb
144
+ - test/support/database.yaml
145
+ - test/support/fake_cmd_runner.rb
146
+ - test/support/test.yaml
147
+ - test/support/test_scripts.rb