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 +9 -0
- data/README.md +83 -0
- data/Rakefile +34 -0
- data/cap-taffy.gemspec +45 -0
- data/lib/cap-taffy/db.rb +239 -0
- data/lib/cap-taffy/parse.rb +53 -0
- data/lib/cap-taffy/ssh.rb +18 -0
- data/lib/cap-taffy.rb +45 -0
- data/spec/cap-taffy/db_spec.rb +287 -0
- data/spec/cap-taffy/parse_spec.rb +57 -0
- data/spec/cap-taffy/ssh_spec.rb +37 -0
- data/spec/cap-taffy_spec.rb +4 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +179 -0
- metadata +117 -0
data/History.txt
ADDED
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
|
data/lib/cap-taffy/db.rb
ADDED
@@ -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
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -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
|
+
|