hsume2-cap-taffy 0.0.2

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/History.txt ADDED
@@ -0,0 +1,9 @@
1
+ == 0.0.2 / 2009-09-14
2
+ * 1 minor enhancement
3
+ * Added authorizing SSH public keys on remote server(s).
4
+
5
+ == 0.0.1 / 2009-09-14
6
+
7
+ * 1 major enhancement
8
+ * Initial release.
9
+ * Added db push/pull.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ Capistrano Taffy (Database, SSH recipes)
2
+ ================================================
3
+
4
+ **Capistrano recipes for deploying databases and other common tasks (managing database.yml, importing/exporting/transfering databases, SSH authorization etc.)**
5
+
6
+ Features
7
+ ------------------------------------------------
8
+
9
+ * Adds database transfer recipes (via [`Taps`]("http://github.com/ricardochimal/taps"))
10
+ * Authorize SSH access
11
+ * Manage `database.yml` (Soon.)
12
+
13
+ [`Taps`]("http://github.com/ricardochimal/taps") is great, but having to SSH into my deployment to run the `Taps` server, as well as
14
+ figure out the proper local/remote database urls, is a pain. I knew the [`Heroku`]("http://github.com/heroku/heroku") gem
15
+ had it figured out; I present the Capistrano friendly version.
16
+
17
+ If you are new to `Taps`, check out this [introduction to `Taps`]("http://adam.blog.heroku.com/past/2009/2/11/taps_for_easy_database_transfers/") by [Adam Wiggins]("http://github.com/adamwiggins").
18
+
19
+ Installation
20
+ ------------------------------------------------
21
+
22
+ gem install cap-taffy
23
+
24
+ Database Transfer
25
+ ------------------------------------------------
26
+
27
+ > _Dependency:_ The [`Taps`]("http://github.com/ricardochimal/taps") gem is required on any server(s) you'll be transferring databases to (`:app` role) including your development machine (where you'll be running `cap` tasks from). Run:
28
+
29
+ > gem install taps
30
+
31
+ > _Dependency:_ The [`Heroku`]("http://github.com/heroku/heroku") gem is required on your development machine.
32
+
33
+ > gem install heroku
34
+
35
+ ### Usage
36
+
37
+ To start, add the following to your `Capfile`
38
+
39
+ require 'cap-taffy/db'
40
+
41
+ `Taffy` will start a `Taps` server on port 5000 by default, so make sure port 5000 (or w/e port you end up using) is open on your `:app` server(s)
42
+
43
+ Then you can use:
44
+
45
+ cap db:push # Or to specify a different port: cap db:push -s taps_port=4321
46
+ cap db:pull # Or to specify a different port: cap db:pull -s taps_port=4321
47
+
48
+
49
+ #### SSH Local Forwarding
50
+
51
+ > Some of you may not be able/want to open up ports on your servers. Instead, you can run:
52
+
53
+ > ssh -N -L[port]:127.0.0.1:[port] [user]@[remote-server]
54
+ > And then run:
55
+
56
+ > cap db:push -s taps_port=[port] -s local=true
57
+ > Substituing the appropriate values for [port], [user], and [remote-server].
58
+ > #### Sample Usage
59
+ > > ssh -N -L4321:127.0.0.1:4321 henry@load-test
60
+ > > cap db:push -s taps_port=4321 -s local=true
61
+
62
+ SSH Access
63
+ ------------------------------------------------
64
+
65
+ #### Usage
66
+
67
+ Add the following to your `Capfile`
68
+
69
+ require 'cap-taffy/ssh'
70
+
71
+ Using a public key generated from `ssh-keygen` (e.g. `ssh-keygen -t rsa`), to authorize access:
72
+
73
+ cap ssh:authorize # authorizes local public key for SSH access to remote server(s)
74
+
75
+
76
+ Managing `database.yml`
77
+ ------------------------------------------------
78
+
79
+ > Much needed and coming soon.
80
+
81
+ Credits
82
+ ------------------------------------------------
83
+ Thanks to developers of the [`Taps`]("http://adam.blog.heroku.com/past/2009/2/11/taps_for_easy_database_transfers/") gem and the [`Heroku`]("http://github.com/heroku/heroku") gem. And of course, Capistrano, awesome!
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ begin
10
+ load 'tasks/setup.rb'
11
+ rescue LoadError
12
+ raise RuntimeError, '### please install the "bones" gem ###'
13
+ end
14
+ end
15
+
16
+ ensure_in_path 'lib'
17
+ require 'cap-taffy'
18
+
19
+ task :default => 'spec:run'
20
+
21
+ PROJ.name = 'cap-taffy'
22
+ PROJ.authors = 'Henry Hsu'
23
+ PROJ.email = 'henry@qlane.com'
24
+ PROJ.url = 'http://by.qlane.com'
25
+ PROJ.version = CapTaffy::VERSION
26
+ PROJ.rubyforge.name = 'cap-taffy'
27
+ PROJ.readme_file = "README.md"
28
+ PROJ.gem.dependencies = ['heroku', 'taps', 'capistrano']
29
+ PROJ.gem.development_dependencies << ["mocha"]
30
+ PROJ.description = "Capistrano recipes for deploying databases and other common tasks."
31
+ PROJ.summary = "Capistrano recipes for deploying databases (managing database.yml, importing/exporting/transfering databases, etc.)"
32
+ PROJ.ignore_file = '.gitignore'
33
+ PROJ.spec.opts << '--color'
34
+ PROJ.exclude << "bin"
data/cap-taffy.gemspec ADDED
@@ -0,0 +1,45 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{cap-taffy}
5
+ s.version = "0.0.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Henry Hsu"]
9
+ s.date = %q{2009-09-17}
10
+ s.description = %q{Capistrano recipes for deploying databases and other common tasks.}
11
+ s.email = %q{henry@qlane.com}
12
+ s.extra_rdoc_files = ["History.txt"]
13
+ s.files = ["History.txt", "README.md", "Rakefile", "cap-taffy.gemspec", "lib/cap-taffy.rb", "lib/cap-taffy/db.rb", "lib/cap-taffy/parse.rb", "lib/cap-taffy/ssh.rb", "spec/cap-taffy/db_spec.rb", "spec/cap-taffy/parse_spec.rb", "spec/cap-taffy/ssh_spec.rb", "spec/cap-taffy_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
14
+ s.homepage = %q{http://by.qlane.com}
15
+ s.rdoc_options = ["--main", "README.md"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{cap-taffy}
18
+ s.rubygems_version = %q{1.3.5}
19
+ s.summary = %q{Capistrano recipes for deploying databases (managing database.yml, importing/exporting/transfering databases, etc.)}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<heroku>, [">= 0"])
27
+ s.add_runtime_dependency(%q<taps>, [">= 0"])
28
+ s.add_runtime_dependency(%q<capistrano>, [">= 0"])
29
+ s.add_development_dependency(%q<bones>, [">= 2.5.1"])
30
+ s.add_development_dependency(%q<mocha>, [">= 0"])
31
+ else
32
+ s.add_dependency(%q<heroku>, [">= 0"])
33
+ s.add_dependency(%q<taps>, [">= 0"])
34
+ s.add_dependency(%q<capistrano>, [">= 0"])
35
+ s.add_dependency(%q<bones>, [">= 2.5.1"])
36
+ s.add_dependency(%q<mocha>, [">= 0"])
37
+ end
38
+ else
39
+ s.add_dependency(%q<heroku>, [">= 0"])
40
+ s.add_dependency(%q<taps>, [">= 0"])
41
+ s.add_dependency(%q<capistrano>, [">= 0"])
42
+ s.add_dependency(%q<bones>, [">= 2.5.1"])
43
+ s.add_dependency(%q<mocha>, [">= 0"])
44
+ end
45
+ end
@@ -0,0 +1,239 @@
1
+ require 'rubygems'
2
+ begin
3
+ gem 'taps', '>= 0.2.8', '< 0.3.0'
4
+ require 'taps/client_session'
5
+ rescue LoadError
6
+ error "Install the Taps gem to use db commands. On most systems this will be:\nsudo gem install taps"
7
+ end
8
+
9
+ require File.join(File.dirname(__FILE__), %w[.. cap-taffy]) unless defined?(CapTaffy)
10
+ require File.join(File.dirname(__FILE__), 'parse')
11
+ require 'digest/sha1'
12
+
13
+ module CapTaffy::Db
14
+ extend self
15
+
16
+ # Detects the local database url for +env+.
17
+ #
18
+ # Looks for <tt>config/database.yml</tt>.
19
+ def local_database_url(env)
20
+ return "" unless File.exists?(Dir.pwd + '/config/database.yml')
21
+ db_config = YAML.load(File.read(Dir.pwd + '/config/database.yml'))
22
+
23
+ CapTaffy::Parse.database_url(db_config, env)
24
+ end
25
+
26
+ # Detects the remote database url for +env+ and the current Capistrano +instance+.
27
+ #
28
+ # Looks for <tt>config/database.yml</tt> in the +current_path+.
29
+ def remote_database_url(instance, env)
30
+ db_yml = instance.capture "cat #{instance.current_path}/config/database.yml"
31
+ db_config = YAML::load(db_yml)
32
+
33
+ CapTaffy::Parse.database_url(db_config, env)
34
+ end
35
+
36
+ # The default server port the Taps server is started on.
37
+ def default_server_port
38
+ 5000
39
+ end
40
+
41
+ # Generates the remote url used by Taps push/pull.
42
+ #
43
+ # ==== Parameters
44
+ #
45
+ # * <tt>:login, :password, :host, :port</tt> - See #run.
46
+ #
47
+ # ==== Examples
48
+ #
49
+ # login = fetch(:user)
50
+ # password = tmp_pass(login) # returns asdkf239udjhdaks (for example)
51
+ # remote_url(:login => login, :password => password, :host => 'load-test') # returns http://henry:asdkf239udjhdaks@load-test:5000
52
+ def remote_url(options={})
53
+ host = options[:host]
54
+ port = options[:port] || default_server_port
55
+ host += ":#{port}"
56
+ url = CapTaffy::Parse.new.uri_hash_to_url('username' => options[:login], 'password' => options[:password], 'host' => host, 'scheme' => 'http', 'path' => '')
57
+
58
+ url.sub(/\/$/, '')
59
+ end
60
+
61
+ # Generates a temporary password to be used for the Taps server command.
62
+ def tmp_pass(user)
63
+ Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{user}--")
64
+ end
65
+
66
+ # A quick start for a Taps client.
67
+ #
68
+ # <tt>local_database_url</tt> and <tt>remote_url</tt> refer to the options for the Taps gem (see #run).
69
+ def taps_client(local_database_url, remote_url, &blk) # :yields: client
70
+ Taps::Config.chunksize = 1000
71
+ Taps::Config.database_url = local_database_url
72
+ Taps::Config.remote_url = remote_url
73
+ Taps::Config.verify_database_url
74
+
75
+ Taps::ClientSession.quickstart do |client|
76
+ yield client
77
+ end
78
+ end
79
+
80
+ # Generates the server command used to start a Taps server
81
+ #
82
+ # ==== Parameters
83
+ # * <tt>:remote_database_url, :login, :password</tt> - See #run.
84
+ # * <tt>:port</tt> - The +port+ the Taps server is on. If given and different from #default_server_port, appends <tt>--port=[port]</tt> to command.
85
+ def server_command(options={})
86
+ remote_database_url, login, password, port = options[:remote_database_url], options[:login], options[:password], options[:port]
87
+ port_argument = ''
88
+ port_argument = " --port=#{port}" if port && port != default_server_port
89
+
90
+ "taps server #{remote_database_url} #{login} #{password}#{port_argument}"
91
+ end
92
+
93
+ # The meat of the operation. Runs operations after setting up the Taps server.
94
+ #
95
+ # 1. Runs the <tt>taps</tt> taps command to start the Taps server (assuming Sinatra is running on Thin)
96
+ # 2. Wait until the server is ready
97
+ # 3. Execute block on Taps client
98
+ # 4. Close the connection(s) and bid farewell.
99
+ #
100
+ # ==== Parameters
101
+ # * <tt>:remote_database_url</tt> - Refers to local database url in the options for the Taps server command (see Taps Options).
102
+ # * <tt>:login</tt> - The login for +host+. Usually what's in <tt>set :user, "the user"</tt> in <tt>deploy.rb</tt>
103
+ # * <tt>:password</tt> - The temporary password for the Taps server.
104
+ # * <tt>:port</tt> - The +port+ the Taps server is on. If not given, defaults to #default_server_port.
105
+ # * <tt>:local_database_url</tt> - Refers to the local database url in the options for Taps client commands (see Taps Options).
106
+ #
107
+ # ==== Taps Options
108
+ #
109
+ # <tt>taps</tt>
110
+ # server <local_database_url> <login> <password> [--port=N] Start a taps database import/export server
111
+ # pull <local_database_url> <remote_url> [--chunksize=N] Pull a database from a taps server
112
+ # push <local_database_url> <remote_url> [--chunksize=N] Push a database to a taps server
113
+ #
114
+ # ==== Examples
115
+ #
116
+ # task :push do
117
+ # login = fetch(:user)
118
+ # password = Time.now.to_s
119
+ # CapTaffy.Db.run(self, { :login => login, :password => password, :remote_database_url => "sqlite://test_production", :local_database_url => "sqlite://test_development" }) do |client|
120
+ # client.cmd_send
121
+ # end
122
+ # end
123
+ def run(instance, options = {} , &blk) # :yields: client
124
+ options[:port] ||= default_server_port
125
+ remote_database_url, login, password, port, local_database_url = options[:remote_database_url], options[:login], options[:password], options[:port], options[:local_database_url]
126
+ force_local = options.delete(:local)
127
+
128
+ data_so_far = ""
129
+ instance.run CapTaffy::Db.server_command(options) do |channel, stream, data|
130
+ data_so_far << data
131
+ if data_so_far.include? ">> Listening on 0.0.0.0:#{port}, CTRL+C to stop"
132
+ host = force_local ? '127.0.0.1' : channel[:host]
133
+ remote_url = CapTaffy::Db.remote_url(options.merge(:host => host))
134
+
135
+ CapTaffy::Db.taps_client(local_database_url, remote_url) do |client|
136
+ yield client
137
+ end
138
+
139
+ data_so_far = ""
140
+ channel.close
141
+ channel[:status] = 0
142
+ end
143
+ end
144
+ end
145
+
146
+ class InvalidURL < RuntimeError # :nodoc:
147
+ end
148
+ end
149
+
150
+ Capistrano::Configuration.instance.load do
151
+ namespace :db do
152
+ # Executes given block.
153
+ # If this is a dry run, any raised exceptions will be caught and +returning+ is returned.
154
+ # If this is not a dry run, any exceptions will be raised as expected.
155
+ def dry_run_safe(returning = nil, &block) # :yields:
156
+ begin
157
+ yield
158
+ rescue Exception => e
159
+ raise e unless dry_run
160
+ return returning
161
+ end
162
+ end
163
+
164
+ task :detect, :roles => :app do
165
+ @remote_database_url = dry_run_safe('') { CapTaffy::Db.remote_database_url(self, 'production') }
166
+ @local_database_url = dry_run_safe('') { CapTaffy::Db.local_database_url('development') }
167
+ end
168
+
169
+ desc <<-DESC
170
+ Push a local database into the app's remote database.
171
+
172
+ Performs push from local development database to remote production database.
173
+ Opens a Taps server on port 5000. (Ensure port is opened on the remote server).
174
+
175
+ # alternately, specify a different port
176
+ cap db:push -s taps_port=4321
177
+
178
+ For the security conscious:
179
+
180
+ # use ssh local forwarding (ensure [port] is available on both endpoints)
181
+ ssh -N -L[port]:127.0.0.1:[port] [user]@[remote-server]
182
+
183
+ # then push locally
184
+ cap db:push -s taps_port=[port] -s local=true
185
+ DESC
186
+ task :push, :roles => :app do
187
+ detect
188
+
189
+ login = fetch(:user)
190
+ password = CapTaffy::Db.tmp_pass(login)
191
+
192
+ logger = Capistrano::Logger.new
193
+ logger.important "Auto-detected remote database: #{@remote_database_url}" if @remote_database_url != ''
194
+ logger.important "Auto-detected local database: #{@local_database_url}" if @local_database_url != ''
195
+
196
+ options = {:remote_database_url => @remote_database_url, :login => login, :password => password, :local_database_url => @local_database_url, :port => variables[:taps_port]}
197
+ options.merge!(:local => true) if variables[:local]
198
+
199
+ CapTaffy::Db.run(self, options) do |client|
200
+ client.cmd_send
201
+ end
202
+ end
203
+
204
+ desc <<-DESC
205
+ Pull the app's database into a local database.
206
+
207
+ Performs pull from remote production database to local development database.
208
+ Opens a Taps server on port 5000. (Ensure port is opened on the remote server).
209
+
210
+ # alternately, specify a different port
211
+ cap db:pull -s taps_port=4321
212
+
213
+ For the security conscious:
214
+
215
+ # use ssh local forwarding (ensure [port] is available on both endpoints)
216
+ ssh -N -L[port]:127.0.0.1:[port] [user]@[remote-server]
217
+
218
+ # then pull locally
219
+ cap db:pull -s taps_port=[port] -s local=true
220
+ DESC
221
+ task :pull, :roles => :app do
222
+ detect
223
+
224
+ login = fetch(:user)
225
+ password = CapTaffy::Db.tmp_pass(login)
226
+
227
+ logger = Capistrano::Logger.new
228
+ logger.important "Auto-detected remote database: #{@remote_database_url}" if @remote_database_url != ''
229
+ logger.important "Auto-detected local database: #{@local_database_url}" if @local_database_url != ''
230
+
231
+ options = {:remote_database_url => @remote_database_url, :login => login, :password => password, :local_database_url => @local_database_url, :port => variables[:taps_port]}
232
+ options.merge!(:local => true) if variables[:local]
233
+
234
+ CapTaffy::Db.run(self, options) do |client|
235
+ client.cmd_receive
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ begin
3
+ require 'heroku'
4
+ require 'heroku/commands/base'
5
+ require 'heroku/commands/db'
6
+ rescue LoadError
7
+ error "Install the Heroku gem. On most systems this will be:\nsudo gem install taps"
8
+ end
9
+
10
+ module CapTaffy
11
+ class Parse < Heroku::Command::Db
12
+ class << self
13
+ attr_accessor :instance
14
+
15
+ # Modified from :parse_database_yml in heroku/command/db.rb
16
+ #
17
+ # Accepts a complete +db_config+ hash and +env+ and parses for a database_url accordingly.
18
+ def database_url(db_config, env)
19
+ raise Invalid, "please pass me a valid Hash loaded from a database YAML file" unless db_config
20
+ conf = db_config[env]
21
+ raise Invalid, "missing '#{env}' database in #{db_config.inspect}" unless conf
22
+
23
+ self.instance ||= CapTaffy::Parse.new
24
+
25
+ case conf['adapter']
26
+ when 'sqlite3'
27
+ return "sqlite://#{conf['database']}"
28
+ when 'postgresql'
29
+ uri_hash = self.instance.conf_to_uri_hash(conf)
30
+ uri_hash['scheme'] = 'postgres'
31
+ return self.instance.uri_hash_to_url(uri_hash)
32
+ else
33
+ return self.instance.uri_hash_to_url(self.instance.conf_to_uri_hash(conf))
34
+ end
35
+ end
36
+ end
37
+
38
+ # Override to do nothing on #new
39
+ def initialize # :nodoc:
40
+
41
+ end
42
+
43
+ # Override to pass-through on #escape
44
+ def escape(string)
45
+ string
46
+ end
47
+
48
+ public :uri_hash_to_url, :conf_to_uri_hash
49
+
50
+ class Invalid < RuntimeError # :nodoc:
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,18 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. cap-taffy]) unless defined?(CapTaffy)
2
+
3
+ module CapTaffy::SSH
4
+
5
+ end
6
+
7
+ Capistrano::Configuration.instance.load do
8
+ namespace :ssh do
9
+ desc <<-DESC
10
+ Authorize SSH access for local computer on remote computers(s).
11
+ DESC
12
+ task :authorize do
13
+ public_key = File.read(File.expand_path(File.join(%w[~/ .ssh id_rsa.pub]))).chop
14
+
15
+ run %Q[if [ ! -f ~/.ssh/authorized_keys ]; then mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys; fi && if [ -z "$(grep "^#{public_key}$" ~/.ssh/authorized_keys)" ]; then echo "#{public_key}" >> ~/.ssh/authorized_keys && echo "Public key on '$CAPISTRANO:HOST$' authorized at '#{Time.now.to_s}'"; else echo "Public key on '$CAPISTRANO:HOST$' is already authorized."; fi]
16
+ end
17
+ end
18
+ end
data/lib/cap-taffy.rb ADDED
@@ -0,0 +1,45 @@
1
+
2
+ module CapTaffy
3
+
4
+ # :stopdoc:
5
+ VERSION = '0.0.2'
6
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
+ # :startdoc:
9
+
10
+ # Returns the version string for the library.
11
+ #
12
+ def self.version
13
+ VERSION
14
+ end
15
+
16
+ # Returns the library path for the module. If any arguments are given,
17
+ # they will be joined to the end of the libray path using
18
+ # <tt>File.join</tt>.
19
+ #
20
+ def self.libpath( *args )
21
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
22
+ end
23
+
24
+ # Returns the lpath for the module. If any arguments are given,
25
+ # they will be joined to the end of the path using
26
+ # <tt>File.join</tt>.
27
+ #
28
+ def self.path( *args )
29
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
30
+ end
31
+
32
+ # Utility method used to require all files ending in .rb that lie in the
33
+ # directory below this file that has the same name as the filename passed
34
+ # in. Optionally, a specific _directory_ name can be passed in such that
35
+ # the _filename_ does not have to be equivalent to the directory.
36
+ #
37
+ def self.require_all_libs_relative_to( fname, dir = nil )
38
+ dir ||= ::File.basename(fname, '.*')
39
+ search_me = ::File.expand_path(
40
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
41
+
42
+ Dir.glob(search_me).sort.each {|rb| require rb}
43
+ end
44
+
45
+ end
@@ -0,0 +1,287 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. spec_helper])
2
+
3
+ module CapTaffy
4
+ describe 'Db' do
5
+ context "when loaded" do
6
+ include CapistranoHelpers
7
+ include TaffyHelpers
8
+
9
+ before do
10
+ CapTaffy.send(:remove_const, "Db") rescue nil
11
+ end
12
+
13
+ it "should load in capistrano configuration instance" do;
14
+ Capistrano::Configuration.instance.expects(:load)
15
+
16
+ load 'lib/cap-taffy/db.rb'
17
+ end
18
+
19
+ it "should define :db namespace" do
20
+ Capistrano::Configuration.instance.expects(:namespace).with(:db)
21
+
22
+ load 'lib/cap-taffy/db.rb'
23
+ end
24
+
25
+ for_task :detect, :roles => :app, :in => :Db, :it => "should be defined" do
26
+ @mod.expects(:remote_database_url).returns("remote_db_url")
27
+ @mod.expects(:local_database_url).returns("local_db_url")
28
+
29
+ load 'lib/cap-taffy/db.rb'
30
+
31
+ @namespace.instance_variable_get(:@remote_database_url).should == "remote_db_url"
32
+ @namespace.instance_variable_get(:@local_database_url).should == "local_db_url"
33
+ end
34
+
35
+ def load_taffy_db # :nodoc:
36
+ with_logger do
37
+ load 'lib/cap-taffy/db.rb'
38
+ end
39
+ end
40
+
41
+ for_task :push, :roles => :app, :in => :Db, :it => "should send taps client cmd_send" do
42
+ options = {:remote_database_url => "remote", :local_database_url => "local", :port => nil, :login => "a_user", :password => "a_pass"}
43
+ @namespace.expects(:detect)
44
+ namespace_with_variables(:taps_port => nil)
45
+ db_with_expected_options(options)
46
+ @mod.expects(:tmp_pass).with(@namespace.fetch(:user)).returns(options[:password])
47
+ @mod.expects(:run).with(@namespace, options).yields(taps_client_who(:expects, :cmd_send))
48
+
49
+ load_taffy_db
50
+ end
51
+
52
+ for_task :push, :roles => :app, :in => :Db, :it => "should use cli argument for port" do
53
+ options = {:remote_database_url => "remote", :local_database_url => "local", :port => 1234, :login => "a_user", :password => "a_pass"}
54
+ @namespace.expects(:detect)
55
+ namespace_with_variables(:taps_port => 1234)
56
+ db_with_expected_options(options)
57
+ @mod.expects(:tmp_pass).with(@namespace.fetch(:user)).returns(options[:password])
58
+ @mod.expects(:run).with(@namespace, options)
59
+
60
+ load_taffy_db
61
+ end
62
+
63
+ for_task :push, :roles => :app, :in => :Db, :it => "should force 127.0.0.1 (local) for ssh local forwarding" do
64
+ options = {:remote_database_url => "remote", :local_database_url => "local", :port => 1234, :login => "a_user", :password => "a_pass"}
65
+ @namespace.expects(:detect)
66
+ namespace_with_variables(:taps_port => 1234, :local => true)
67
+ db_with_expected_options(options)
68
+ @mod.expects(:tmp_pass).with(@namespace.fetch(:user)).returns(options[:password])
69
+ @mod.expects(:run).with(@namespace, options.merge(:local => true))
70
+
71
+ load_taffy_db
72
+ end
73
+
74
+ for_task :pull, :roles => :app, :in => :Db, :it => "should send taps client cmd_receive" do
75
+ options = {:remote_database_url => "remote", :local_database_url => "local", :port => nil, :login => "a_user", :password => "a_pass"}
76
+ @namespace.expects(:detect)
77
+ namespace_with_variables(:taps_port => nil)
78
+ db_with_expected_options(options)
79
+ @mod.expects(:tmp_pass).with(@namespace.fetch(:user)).returns(options[:password])
80
+ @mod.expects(:run).with(@namespace, options).yields(taps_client_who(:expects, :cmd_receive))
81
+
82
+ load_taffy_db
83
+ end
84
+ end
85
+
86
+ context "after capistrano" do
87
+ include CapistranoHelpers
88
+ include TaffyHelpers
89
+
90
+ before do
91
+ Capistrano::Configuration.instance.expects(:namespace).with(:db)
92
+ CapTaffy.send(:remove_const, "Db") rescue nil
93
+ load 'lib/cap-taffy/db.rb'
94
+
95
+ @conf = {"test"=>
96
+ {"reconnect"=>false,
97
+ "encoding"=>"utf8",
98
+ "username"=>"root",
99
+ "adapter"=>"postgresql",
100
+ "database"=>"test_test",
101
+ "pool"=>5,
102
+ "password"=>"root",
103
+ "host"=>"localhost"}}
104
+
105
+ @options = {:remote_database_url => "remote", :local_database_url => "local", :port => nil, :login => "a_user", :password => "a_pass"}
106
+ end
107
+
108
+ it "should detect local database url" do
109
+ File.expects(:exists?).returns(true)
110
+ File.expects(:read).returns(mock())
111
+ YAML.expects(:load).returns(@conf)
112
+ env = 'test'
113
+ Parse.expects(:database_url).with(@conf, env)
114
+
115
+ Db.local_database_url(env)
116
+ end
117
+
118
+ it "should detect remote database url" do
119
+ instance = mock()
120
+ instance.stubs(:current_path).returns("/home/user/public_html/current")
121
+ instance.expects(:capture).with("cat /home/user/public_html/current/config/database.yml")
122
+ YAML.expects(:load).returns(@conf)
123
+ env = 'test'
124
+ Parse.expects(:database_url).with(@conf, env)
125
+
126
+ Db.remote_database_url(instance, env)
127
+ end
128
+
129
+ it "should create temporary password from time and user" do
130
+ Time.stubs(:now).returns("asdfasdfasdf")
131
+ user = "me"
132
+ tmp_pass = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{user}--")
133
+
134
+ Db.tmp_pass(user).should == tmp_pass
135
+ end
136
+
137
+ it "should expose a basic taps client" do
138
+ client = mock()
139
+ local_database_url = mock()
140
+ remote_url = mock()
141
+ Taps::Config.expects(:chunksize=).with(1000)
142
+ Taps::Config.expects(:database_url=).with(local_database_url)
143
+ Taps::Config.expects(:remote_url=).with(remote_url)
144
+ Taps::Config.expects(:verify_database_url)
145
+ client.expects(:do_something)
146
+
147
+ Taps::ClientSession.expects(:quickstart).yields(client)
148
+
149
+ Db.taps_client(local_database_url, remote_url) do |client|
150
+ client.do_something
151
+ end
152
+ end
153
+
154
+ it "should define default port" do
155
+ Db.default_server_port.should == 5000
156
+ end
157
+
158
+ it "should build server command" do
159
+ remote_database_url, login, password, port = @options[:remote_database_url], @options[:login], @options[:password], @options[:port]
160
+
161
+ Db.server_command(@options).should == "taps server #{remote_database_url} #{login} #{password}"
162
+ end
163
+
164
+ it "should build server command with port" do
165
+ @options[:port] = 1234
166
+ remote_database_url, login, password, port = @options[:remote_database_url], @options[:login], @options[:password], @options[:port]
167
+
168
+ Db.server_command(@options).should == "taps server #{remote_database_url} #{login} #{password} --port=#{port}"
169
+ end
170
+
171
+ it "should build server command without port if same as default port" do
172
+ @options[:port] = 5000
173
+ remote_database_url, login, password = @options[:remote_database_url], @options[:login], @options[:password]
174
+
175
+ Db.server_command(@options).should == "taps server #{remote_database_url} #{login} #{password}"
176
+ end
177
+
178
+ def parser_expects_uri_hash_to_url_with(login, password, host)
179
+ parser = mock()
180
+ Parse.expects(:new).at_least_once.returns(parser)
181
+ parser.expects(:uri_hash_to_url).
182
+ with('username' => login, 'password' => password, 'host' => host, 'scheme' => 'http', 'path' => '')
183
+ end
184
+
185
+ it "should build remote url (with some help)" do
186
+ @options[:host] = "127.0.0.1"
187
+ parser_expects_uri_hash_to_url_with(@options[:login], @options[:password], "#{@options[:host]}:#{Db.default_server_port}").returns("remote_url/")
188
+
189
+ Db.remote_url(@options)
190
+ end
191
+
192
+ it "should build remote url with different port" do
193
+ @options[:host] = "127.0.0.1"
194
+ @options[:port] = 1234
195
+ parser_expects_uri_hash_to_url_with(@options[:login], @options[:password], "#{@options[:host]}:#{@options[:port]}").returns("remote_url")
196
+
197
+ Db.remote_url(@options)
198
+ end
199
+
200
+ it "should remove trailing slash from remote url" do
201
+ @options[:host] = "127.0.0.1"
202
+ parser_expects_uri_hash_to_url_with(@options[:login], @options[:password], "#{@options[:host]}:#{Db.default_server_port}").returns("remote_url/")
203
+
204
+ Db.remote_url(@options).should == "remote_url"
205
+ end
206
+
207
+ running_db_it "should run with capistrano" do
208
+ capistrano_run_with(Db.server_command(@options))
209
+
210
+ Db.run(@capistrano, @options)
211
+ end
212
+
213
+ running_db_it "should do something to taps client" do
214
+ channel, stream, data = simulating_run_loop_with :data => ">> Listening on 0.0.0.0:5000, CTRL+C to stop\r\n" do
215
+ capistrano_run_with(Db.server_command(@options))
216
+ end
217
+ channel.expects(:close)
218
+
219
+ Db.expects(:remote_url).with(@options.merge(:host => channel[:host], :port => 5000)).returns("remote_url")
220
+ Db.expects(:taps_client).with("local", "remote_url").yields(taps_client_who(:expects, :do_something))
221
+ Db.run(@capistrano, @options) do |client|
222
+ client.do_something
223
+ end
224
+
225
+ channel[:status].should == 0
226
+ end
227
+
228
+ running_db_it "should run taffy on different port" do
229
+ @options[:port] = 1234
230
+
231
+ channel, stream, data = simulating_run_loop_with :data => ">> Listening on 0.0.0.0:1234, CTRL+C to stop\r\n" do
232
+ capistrano_run_with(Db.server_command(@options))
233
+ end
234
+ channel.expects(:close)
235
+ Db.expects(:remote_url).with(@options.merge(:host => channel[:host])).returns("remote_url")
236
+ Db.expects(:taps_client).with("local", "remote_url").yields(taps_client_who(:expects, :do_something))
237
+
238
+ Db.run(@capistrano, @options) do |client|
239
+ client.do_something
240
+ end
241
+
242
+ channel[:status].should == 0
243
+ end
244
+
245
+ running_db_it "should not do anything until taps sinatra server is running" do
246
+ simulating_run_loop_with :data => "asdfasdf" do
247
+ capistrano_run_with(Db.server_command(@options))
248
+ end
249
+
250
+ client = mock()
251
+ client.expects(:do_something).never
252
+
253
+ Db.run(@capistrano, @options) do |client|
254
+ client.do_something
255
+ end
256
+
257
+ channel, stream, data = simulating_run_loop_with :data => ">> Listening on 0.0.0.0:5000, CTRL+C to stop\r\n" do
258
+ capistrano_run_with(Db.server_command(@options))
259
+ end
260
+ channel.expects(:close)
261
+ Db.expects(:remote_url).with(@options.merge(:host => channel[:host], :port => 5000)).returns("remote_url")
262
+ Db.expects(:taps_client).with("local", "remote_url").yields(taps_client_who(:expects, :do_something))
263
+
264
+ Db.run(@capistrano, @options) do |client|
265
+ client.do_something
266
+ end
267
+
268
+ channel[:status].should == 0
269
+ end
270
+
271
+ running_db_it "should force 127.0.0.1 (local) for remote url" do
272
+ channel, stream, data = simulating_run_loop_with :data => ">> Listening on 0.0.0.0:5000, CTRL+C to stop\r\n" do
273
+ capistrano_run_with(Db.server_command(@options))
274
+ end
275
+ channel.expects(:close)
276
+
277
+ Db.expects(:remote_url).with(@options.merge(:host => '127.0.0.1', :port => 5000)).returns("remote_url")
278
+ Db.expects(:taps_client).with("local", "remote_url").yields(taps_client_who(:expects, :do_something))
279
+ Db.run(@capistrano, @options.merge(:local => true)) do |client|
280
+ client.do_something
281
+ end
282
+
283
+ channel[:status].should == 0
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,57 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. spec_helper])
2
+ require 'lib/cap-taffy/parse'
3
+
4
+ module CapTaffy
5
+ describe Parse do
6
+ before do
7
+ @database_config = {"production"=>
8
+ {"reconnect"=>false,
9
+ "encoding"=>"utf8",
10
+ "username"=>"root",
11
+ "adapter"=>"mysql",
12
+ "database"=>"test_production",
13
+ "pool"=>5,
14
+ "password"=>"root",
15
+ "host"=>"localhost"},
16
+ "development"=>
17
+ {"username"=>"root",
18
+ "adapter"=>"sqlite3",
19
+ "database"=>"test_development",
20
+ "password"=>"root"},
21
+ "test"=>
22
+ {"reconnect"=>false,
23
+ "encoding"=>"utf8",
24
+ "username"=>"root",
25
+ "adapter"=>"postgresql",
26
+ "database"=>"test_test",
27
+ "pool"=>5,
28
+ "password"=>"root",
29
+ "host"=>"localhost"}}
30
+ end
31
+
32
+ it "should do pass-through for escape" do
33
+ Parse.new.escape("something").should == "something"
34
+ end
35
+
36
+ it "should parse database config for sqlite" do
37
+ Parse.expects(:conf_to_uri_hash).never
38
+ Parse.database_url(@database_config, 'development').should == "sqlite://test_development"
39
+ end
40
+
41
+ it "should parse database config for postgresql" do
42
+ Parse.database_url(@database_config, 'test').should == "postgres://root:root@localhost/test_test?encoding=utf8"
43
+ end
44
+
45
+ it "should parse database config for mysql" do
46
+ Parse.database_url(@database_config, 'production').should == "mysql://root:root@localhost/test_production?encoding=utf8"
47
+ end
48
+
49
+ it "should raise invalid conf if so" do
50
+ lambda { Parse.database_url(nil, nil) }.should raise_error(Parse::Invalid)
51
+ end
52
+
53
+ it "should raise invalid conf if so" do
54
+ lambda { Parse.database_url(@database_config, nil) }.should raise_error(Parse::Invalid)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,37 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. spec_helper])
2
+
3
+ module CapTaffy
4
+ describe 'SSH' do
5
+ include CapistranoHelpers
6
+
7
+ before do
8
+ CapTaffy.send(:remove_const, "SSH") rescue nil
9
+ end
10
+
11
+ it "should load in capistrano configuration instance" do;
12
+ Capistrano::Configuration.instance.expects(:load)
13
+
14
+ load 'lib/cap-taffy/ssh.rb'
15
+ end
16
+
17
+ it "should define :db namespace" do
18
+ Capistrano::Configuration.instance.expects(:namespace).with(:ssh)
19
+
20
+ load 'lib/cap-taffy/ssh.rb'
21
+ end
22
+
23
+ def simulating_line(line, &blk)
24
+ blk.call.then.yields(line)
25
+ line
26
+ end
27
+
28
+ for_task :authorize, :in => :SSH, :it => "should authorize on each server" do
29
+ public_key = "ssh-key2\n"
30
+ File.expects(:read).with(File.expand_path(File.join(%w[~/ .ssh id_rsa.pub]))).returns(public_key)
31
+
32
+ run_with(@namespace, anything) # Don't rly wna test bash scripts
33
+
34
+ load 'lib/cap-taffy/ssh.rb'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,4 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ describe CapTaffy do
4
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,179 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib cap-taffy]))
2
+
3
+ module Capistrano # :nodoc:
4
+ end
5
+
6
+ Spec::Runner.configure do |config|
7
+ # == Mock Framework
8
+ #
9
+ # RSpec uses it's own mocking framework by default. If you prefer to
10
+ # use mocha, flexmock or RR, uncomment the appropriate line:
11
+ #
12
+ config.mock_with :mocha
13
+ # config.mock_with :flexmock
14
+ # config.mock_with :rr
15
+ end
16
+
17
+ Capistrano.send(:remove_const, "Configuration") rescue nil
18
+ Capistrano.const_set("Configuration", Class.new)
19
+ Capistrano::Configuration.class_eval do
20
+ def self.instance
21
+ @instance ||= Capistrano::Configuration.new
22
+ end
23
+
24
+ def load(*args, &block)
25
+ instance_eval(&block) if block_given?
26
+ end
27
+
28
+ def namespaces
29
+ @namespaces ||= {}
30
+ end
31
+
32
+ def namespace(name, &block)
33
+ namespaces[name].instance_eval(&block)
34
+ end
35
+ end
36
+
37
+ class String # :nodoc:
38
+ def demodulize
39
+ gsub(/^.*::/, '')
40
+ end
41
+ end
42
+
43
+ module CapistranoHelpers
44
+ def self.included(base) # :nodoc:
45
+ base.extend CapistranoHelpers::ClassMethods
46
+ end
47
+
48
+ # Stubs the Capistrano Logger and yields to the block
49
+ def with_logger(&blk) # :yields:
50
+ logger_class = Class.new
51
+ logger = mock()
52
+ logger.stub_everything
53
+ logger_class.stubs(:new).returns(logger)
54
+ Capistrano.const_set("Logger", logger_class)
55
+ yield
56
+ ensure
57
+ Capistrano.send(:remove_const, "Logger") rescue nil
58
+ end
59
+
60
+ # Helper for common operations in db tasks
61
+ def db_with_expected_options(options) # :nodoc:
62
+ @namespace.stubs(:fetch).with(:user).returns(options[:login])
63
+ @namespace.instance_variable_set(:@remote_database_url, options[:remote_database_url])
64
+ @namespace.instance_variable_set(:@local_database_url, options[:local_database_url])
65
+ end
66
+
67
+ # Stubs the variables hash used by Capistrano to accept command-line parameters
68
+ def namespace_with_variables(variables) # :nodoc:
69
+ @namespace.stubs(:variables).returns(variables)
70
+ end
71
+
72
+ # Creates an expectation for the Capistrano namespace/task <tt>instance</tt> for the <tt>:run</tt> action with <tt>*args</tt>.
73
+ def run_with(instance, *args)
74
+ instance.expects(:run).with(*args)
75
+ end
76
+
77
+ # The Capistrano <tt>:run</tt> action loops with <tt>channel</tt>, <tt>stream</tt>, and <tt>data</tt> until the channel is closed.
78
+ #
79
+ # Passing in a <tt>:run</tt> expectation, modifies the expectation such that each subsequent invocation ("loop") yields <tt>channel</tt>, <tt>stream</tt>, and <tt>data</tt>
80
+ #
81
+ # ==== Parameters
82
+ #
83
+ # * <tt>:channel</tt> - A hash containing <tt>:host</tt>.
84
+ # * <tt>:stream</tt> - A stream object.
85
+ # * <tt>:data</tt> - A data object, usually a String.
86
+ def simulating_run_loop_with(options={}, &blk) # :yields:
87
+ channel = options[:channel] || {:host => "192.168.1.20"}
88
+ stream = options[:stream]
89
+ data = options[:data]
90
+
91
+ blk.call.then.yields(channel, stream, data)
92
+ [channel, stream, data]
93
+ end
94
+
95
+ module ClassMethods
96
+ # Used in specs to test Capistrano tasks.
97
+ #
98
+ #
99
+ # Code defined in the task will be executed automatically on load. (Note the <tt>yields</tt>, see Mocha#yields[http://mocha.rubyforge.org/classes/Mocha/Expectation.html#M000043])
100
+ #
101
+ # ==== Parameters
102
+ #
103
+ # * <tt>:it</tt> - The description for the current example group.
104
+ # * <tt>:in</tt> - Specifies the module under test (as well as the namespace the task is defined in). The namespace will be deduced with the downcase of <tt>:in</tt>
105
+ #
106
+ # ==== Examples
107
+ #
108
+ # for_task :detect, :roles => :app, :in => :Somewhere, :it => "should so something" do
109
+ # @namespace # refers to the current task under test (in Capistrano tasks are executed on Capistrano::Namespaces::Namespace instances)
110
+ # @mod # refers to module CapTaffy::Somewhere
111
+ # end
112
+ def for_task(task_name, options, &block)
113
+ description = options.delete(:it)
114
+ namespace = options.delete(:in)
115
+
116
+ context ":#{task_name.to_s} task" do
117
+ before do
118
+ @namespace = Capistrano::Configuration.instance.namespaces[namespace.to_s.downcase.to_sym] = mock()
119
+ @namespace.stubs(:desc)
120
+ @namespace.stubs(:task)
121
+ args = [task_name, options]
122
+ unless options.empty?
123
+ @namespace.expects(:task).with(*args).yields
124
+ else
125
+ @namespace.expects(:task).with(task_name).yields
126
+ end
127
+
128
+ @mod = Module.new # The module under test
129
+ CapTaffy.const_set(namespace, @mod)
130
+ end
131
+
132
+ it description, &block
133
+
134
+ after do
135
+ const_name = @mod.to_s.demodulize
136
+ CapTaffy.send(:remove_const, const_name)
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ module TaffyHelpers
144
+ def self.included(base) # :nodoc:
145
+ base.extend TaffyHelpers::ClassMethods
146
+ end
147
+
148
+ # A simple helper for mocking a quick object
149
+ #
150
+ # Usage:
151
+ # taps_client_who(:expects, :do_something)
152
+ def taps_client_who(method_symbol, *args)
153
+ client = mock()
154
+ client.send(method_symbol, *args)
155
+ client
156
+ end
157
+
158
+ module ClassMethods
159
+ # A wrapper for running CapTaffy::Db::run
160
+ def running_db_it(message, &blk)
161
+ context "when running db" do
162
+ before do
163
+ @capistrano = mock()
164
+ end
165
+
166
+ # See CapistranoHelpers
167
+ def capistrano_run_with(*args)
168
+ run_with(@capistrano, *args)
169
+ end
170
+
171
+ it message, &blk
172
+
173
+ after do
174
+
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hsume2-cap-taffy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Henry Hsu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-17 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: heroku
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: taps
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: capistrano
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: bones
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.5.1
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: mocha
57
+ type: :development
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ description: Capistrano recipes for deploying databases and other common tasks.
66
+ email: henry@qlane.com
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ extra_rdoc_files:
72
+ - History.txt
73
+ files:
74
+ - History.txt
75
+ - README.md
76
+ - Rakefile
77
+ - cap-taffy.gemspec
78
+ - lib/cap-taffy.rb
79
+ - lib/cap-taffy/db.rb
80
+ - lib/cap-taffy/parse.rb
81
+ - lib/cap-taffy/ssh.rb
82
+ - spec/cap-taffy/db_spec.rb
83
+ - spec/cap-taffy/parse_spec.rb
84
+ - spec/cap-taffy/ssh_spec.rb
85
+ - spec/cap-taffy_spec.rb
86
+ - spec/spec.opts
87
+ - spec/spec_helper.rb
88
+ has_rdoc: false
89
+ homepage: http://by.qlane.com
90
+ licenses:
91
+ post_install_message:
92
+ rdoc_options:
93
+ - --main
94
+ - README.md
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: "0"
102
+ version:
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ version:
109
+ requirements: []
110
+
111
+ rubyforge_project: cap-taffy
112
+ rubygems_version: 1.3.5
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Capistrano recipes for deploying databases (managing database.yml, importing/exporting/transfering databases, etc.)
116
+ test_files: []
117
+