hsume2-cap-taffy 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/README.md +20 -4
- data/Rakefile +1 -0
- data/cap-taffy.gemspec +5 -2
- data/lib/cap-taffy.rb +1 -1
- data/lib/cap-taffy/db.rb +70 -9
- data/lib/cap-taffy/parse.rb +7 -3
- data/lib/cap-taffy/ssh.rb +18 -0
- data/spec/cap-taffy/db_spec.rb +52 -47
- data/spec/cap-taffy/ssh_spec.rb +37 -0
- data/spec/cap-taffy_spec.rb +0 -3
- data/spec/spec_helper.rb +84 -39
- metadata +15 -2
data/History.txt
CHANGED
data/README.md
CHANGED
@@ -7,6 +7,7 @@ Features
|
|
7
7
|
------------------------------------------------
|
8
8
|
|
9
9
|
* Adds database transfer recipes (via [`Taps`]("http://github.com/ricardochimal/taps"))
|
10
|
+
* Authorize SSH access
|
10
11
|
* Manage `database.yml` (Soon.)
|
11
12
|
|
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
|
@@ -20,11 +21,9 @@ Installation
|
|
20
21
|
|
21
22
|
gem install cap-taffy
|
22
23
|
|
23
|
-
|
24
|
+
Database Transfer
|
24
25
|
------------------------------------------------
|
25
26
|
|
26
|
-
### `Taffy`: Database Transfer
|
27
|
-
|
28
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:
|
29
28
|
|
30
29
|
> gem install taps
|
@@ -33,6 +32,8 @@ Usage
|
|
33
32
|
|
34
33
|
> gem install heroku
|
35
34
|
|
35
|
+
### Usage
|
36
|
+
|
36
37
|
To start, add the following to your `Capfile`
|
37
38
|
|
38
39
|
require 'cap-taffy/db'
|
@@ -58,7 +59,22 @@ Then you can use:
|
|
58
59
|
> > ssh -N -L4321:127.0.0.1:4321 henry@load-test
|
59
60
|
> > cap db:push -s taps_port=4321 -s local=true
|
60
61
|
|
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
|
+
------------------------------------------------
|
62
78
|
|
63
79
|
> Much needed and coming soon.
|
64
80
|
|
data/Rakefile
CHANGED
@@ -26,6 +26,7 @@ PROJ.version = CapTaffy::VERSION
|
|
26
26
|
PROJ.rubyforge.name = 'cap-taffy'
|
27
27
|
PROJ.readme_file = "README.md"
|
28
28
|
PROJ.gem.dependencies = ['heroku', 'taps', 'capistrano']
|
29
|
+
PROJ.gem.development_dependencies << ["mocha"]
|
29
30
|
PROJ.description = "Capistrano recipes for deploying databases and other common tasks."
|
30
31
|
PROJ.summary = "Capistrano recipes for deploying databases (managing database.yml, importing/exporting/transfering databases, etc.)"
|
31
32
|
PROJ.ignore_file = '.gitignore'
|
data/cap-taffy.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{cap-taffy}
|
5
|
-
s.version = "1.0.
|
5
|
+
s.version = "1.0.1"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Henry Hsu"]
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.description = %q{Capistrano recipes for deploying databases and other common tasks.}
|
11
11
|
s.email = %q{henry@qlane.com}
|
12
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", "spec/cap-taffy/db_spec.rb", "spec/cap-taffy/parse_spec.rb", "spec/cap-taffy_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
|
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
14
|
s.homepage = %q{http://by.qlane.com}
|
15
15
|
s.rdoc_options = ["--main", "README.md"]
|
16
16
|
s.require_paths = ["lib"]
|
@@ -27,16 +27,19 @@ Gem::Specification.new do |s|
|
|
27
27
|
s.add_runtime_dependency(%q<taps>, [">= 0"])
|
28
28
|
s.add_runtime_dependency(%q<capistrano>, [">= 0"])
|
29
29
|
s.add_development_dependency(%q<bones>, [">= 2.5.1"])
|
30
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
30
31
|
else
|
31
32
|
s.add_dependency(%q<heroku>, [">= 0"])
|
32
33
|
s.add_dependency(%q<taps>, [">= 0"])
|
33
34
|
s.add_dependency(%q<capistrano>, [">= 0"])
|
34
35
|
s.add_dependency(%q<bones>, [">= 2.5.1"])
|
36
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
35
37
|
end
|
36
38
|
else
|
37
39
|
s.add_dependency(%q<heroku>, [">= 0"])
|
38
40
|
s.add_dependency(%q<taps>, [">= 0"])
|
39
41
|
s.add_dependency(%q<capistrano>, [">= 0"])
|
40
42
|
s.add_dependency(%q<bones>, [">= 2.5.1"])
|
43
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
41
44
|
end
|
42
45
|
end
|
data/lib/cap-taffy.rb
CHANGED
data/lib/cap-taffy/db.rb
CHANGED
@@ -6,30 +6,49 @@ rescue LoadError
|
|
6
6
|
error "Install the Taps gem to use db commands. On most systems this will be:\nsudo gem install taps"
|
7
7
|
end
|
8
8
|
|
9
|
+
require File.join(File.dirname(__FILE__), %w[.. cap-taffy]) unless defined?(CapTaffy)
|
9
10
|
require File.join(File.dirname(__FILE__), 'parse')
|
10
11
|
require 'digest/sha1'
|
11
12
|
|
12
13
|
module CapTaffy::Db
|
13
14
|
extend self
|
14
15
|
|
15
|
-
|
16
|
+
# Detects the local database url for +env+.
|
17
|
+
#
|
18
|
+
# Looks for <tt>config/database.yml</tt>.
|
19
|
+
def local_database_url(env)
|
16
20
|
return "" unless File.exists?(Dir.pwd + '/config/database.yml')
|
17
21
|
db_config = YAML.load(File.read(Dir.pwd + '/config/database.yml'))
|
18
22
|
|
19
23
|
CapTaffy::Parse.database_url(db_config, env)
|
20
24
|
end
|
21
25
|
|
22
|
-
|
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)
|
23
30
|
db_yml = instance.capture "cat #{instance.current_path}/config/database.yml"
|
24
31
|
db_config = YAML::load(db_yml)
|
25
32
|
|
26
33
|
CapTaffy::Parse.database_url(db_config, env)
|
27
34
|
end
|
28
35
|
|
36
|
+
# The default server port the Taps server is started on.
|
29
37
|
def default_server_port
|
30
38
|
5000
|
31
39
|
end
|
32
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
|
33
52
|
def remote_url(options={})
|
34
53
|
host = options[:host]
|
35
54
|
port = options[:port] || default_server_port
|
@@ -39,11 +58,15 @@ module CapTaffy::Db
|
|
39
58
|
url.sub(/\/$/, '')
|
40
59
|
end
|
41
60
|
|
61
|
+
# Generates a temporary password to be used for the Taps server command.
|
42
62
|
def tmp_pass(user)
|
43
63
|
Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{user}--")
|
44
64
|
end
|
45
65
|
|
46
|
-
|
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
|
47
70
|
Taps::Config.chunksize = 1000
|
48
71
|
Taps::Config.database_url = local_database_url
|
49
72
|
Taps::Config.remote_url = remote_url
|
@@ -54,7 +77,11 @@ module CapTaffy::Db
|
|
54
77
|
end
|
55
78
|
end
|
56
79
|
|
57
|
-
# server
|
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.
|
58
85
|
def server_command(options={})
|
59
86
|
remote_database_url, login, password, port = options[:remote_database_url], options[:login], options[:password], options[:port]
|
60
87
|
port_argument = ''
|
@@ -63,7 +90,37 @@ module CapTaffy::Db
|
|
63
90
|
"taps server #{remote_database_url} #{login} #{password}#{port_argument}"
|
64
91
|
end
|
65
92
|
|
66
|
-
|
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
|
67
124
|
options[:port] ||= default_server_port
|
68
125
|
remote_database_url, login, password, port, local_database_url = options[:remote_database_url], options[:login], options[:password], options[:port], options[:local_database_url]
|
69
126
|
force_local = options.delete(:local)
|
@@ -86,12 +143,16 @@ module CapTaffy::Db
|
|
86
143
|
end
|
87
144
|
end
|
88
145
|
|
89
|
-
class InvalidURL < RuntimeError
|
146
|
+
class InvalidURL < RuntimeError # :nodoc:
|
147
|
+
end
|
90
148
|
end
|
91
149
|
|
92
150
|
Capistrano::Configuration.instance.load do
|
93
151
|
namespace :db do
|
94
|
-
|
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:
|
95
156
|
begin
|
96
157
|
yield
|
97
158
|
rescue Exception => e
|
@@ -101,8 +162,8 @@ Capistrano::Configuration.instance.load do
|
|
101
162
|
end
|
102
163
|
|
103
164
|
task :detect, :roles => :app do
|
104
|
-
@remote_database_url = dry_run_safe('') { CapTaffy::Db.
|
105
|
-
@local_database_url = dry_run_safe('') { CapTaffy::Db.
|
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') }
|
106
167
|
end
|
107
168
|
|
108
169
|
desc <<-DESC
|
data/lib/cap-taffy/parse.rb
CHANGED
@@ -13,6 +13,8 @@ module CapTaffy
|
|
13
13
|
attr_accessor :instance
|
14
14
|
|
15
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.
|
16
18
|
def database_url(db_config, env)
|
17
19
|
raise Invalid, "please pass me a valid Hash loaded from a database YAML file" unless db_config
|
18
20
|
conf = db_config[env]
|
@@ -33,17 +35,19 @@ module CapTaffy
|
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
|
-
|
38
|
+
# Override to do nothing on #new
|
39
|
+
def initialize # :nodoc:
|
37
40
|
|
38
41
|
end
|
39
42
|
|
40
|
-
#
|
43
|
+
# Override to pass-through on #escape
|
41
44
|
def escape(string)
|
42
45
|
string
|
43
46
|
end
|
44
47
|
|
45
48
|
public :uri_hash_to_url, :conf_to_uri_hash
|
46
49
|
|
47
|
-
class Invalid < RuntimeError
|
50
|
+
class Invalid < RuntimeError # :nodoc:
|
51
|
+
end
|
48
52
|
end
|
49
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/spec/cap-taffy/db_spec.rb
CHANGED
@@ -22,64 +22,69 @@ module CapTaffy
|
|
22
22
|
load 'lib/cap-taffy/db.rb'
|
23
23
|
end
|
24
24
|
|
25
|
-
for_task :detect, :roles => :app, :it => "should be defined" do
|
26
|
-
@
|
27
|
-
@
|
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
28
|
|
29
29
|
load 'lib/cap-taffy/db.rb'
|
30
30
|
|
31
|
-
@
|
32
|
-
@
|
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
33
|
end
|
34
34
|
|
35
|
-
|
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
|
36
42
|
options = {:remote_database_url => "remote", :local_database_url => "local", :port => nil, :login => "a_user", :password => "a_pass"}
|
37
|
-
@
|
43
|
+
@namespace.expects(:detect)
|
38
44
|
namespace_with_variables(:taps_port => nil)
|
39
|
-
|
40
|
-
|
41
|
-
@
|
42
|
-
@db_mod.expects(:run).with(@namespace_db, options).yields(taps_client_who(:expects, :cmd_send))
|
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))
|
43
48
|
|
44
|
-
|
49
|
+
load_taffy_db
|
45
50
|
end
|
46
51
|
|
47
|
-
for_task :push, :roles => :app, :it => "should use cli argument for port" do
|
52
|
+
for_task :push, :roles => :app, :in => :Db, :it => "should use cli argument for port" do
|
48
53
|
options = {:remote_database_url => "remote", :local_database_url => "local", :port => 1234, :login => "a_user", :password => "a_pass"}
|
49
|
-
@
|
54
|
+
@namespace.expects(:detect)
|
50
55
|
namespace_with_variables(:taps_port => 1234)
|
51
|
-
|
52
|
-
@
|
53
|
-
@
|
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)
|
54
59
|
|
55
|
-
|
60
|
+
load_taffy_db
|
56
61
|
end
|
57
62
|
|
58
|
-
for_task :push, :roles => :app, :it => "should force 127.0.0.1 (local) for ssh local forwarding" do
|
63
|
+
for_task :push, :roles => :app, :in => :Db, :it => "should force 127.0.0.1 (local) for ssh local forwarding" do
|
59
64
|
options = {:remote_database_url => "remote", :local_database_url => "local", :port => 1234, :login => "a_user", :password => "a_pass"}
|
60
|
-
@
|
65
|
+
@namespace.expects(:detect)
|
61
66
|
namespace_with_variables(:taps_port => 1234, :local => true)
|
62
|
-
|
63
|
-
@
|
64
|
-
@
|
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))
|
65
70
|
|
66
|
-
|
71
|
+
load_taffy_db
|
67
72
|
end
|
68
73
|
|
69
|
-
for_task :pull, :roles => :app, :it => "should send taps client cmd_receive" do
|
74
|
+
for_task :pull, :roles => :app, :in => :Db, :it => "should send taps client cmd_receive" do
|
70
75
|
options = {:remote_database_url => "remote", :local_database_url => "local", :port => nil, :login => "a_user", :password => "a_pass"}
|
71
|
-
@
|
76
|
+
@namespace.expects(:detect)
|
72
77
|
namespace_with_variables(:taps_port => nil)
|
73
|
-
|
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))
|
74
81
|
|
75
|
-
|
76
|
-
@db_mod.expects(:run).with(@namespace_db, options).yields(taps_client_who(:expects, :cmd_receive))
|
77
|
-
|
78
|
-
load_taffy
|
82
|
+
load_taffy_db
|
79
83
|
end
|
80
84
|
end
|
81
85
|
|
82
86
|
context "after capistrano" do
|
87
|
+
include CapistranoHelpers
|
83
88
|
include TaffyHelpers
|
84
89
|
|
85
90
|
before do
|
@@ -107,7 +112,7 @@ module CapTaffy
|
|
107
112
|
env = 'test'
|
108
113
|
Parse.expects(:database_url).with(@conf, env)
|
109
114
|
|
110
|
-
Db.
|
115
|
+
Db.local_database_url(env)
|
111
116
|
end
|
112
117
|
|
113
118
|
it "should detect remote database url" do
|
@@ -118,7 +123,7 @@ module CapTaffy
|
|
118
123
|
env = 'test'
|
119
124
|
Parse.expects(:database_url).with(@conf, env)
|
120
125
|
|
121
|
-
Db.
|
126
|
+
Db.remote_database_url(instance, env)
|
122
127
|
end
|
123
128
|
|
124
129
|
it "should create temporary password from time and user" do
|
@@ -174,12 +179,12 @@ module CapTaffy
|
|
174
179
|
parser = mock()
|
175
180
|
Parse.expects(:new).at_least_once.returns(parser)
|
176
181
|
parser.expects(:uri_hash_to_url).
|
177
|
-
with('username' => login, 'password' => password, 'host' => host, 'scheme' => 'http', 'path' => '')
|
182
|
+
with('username' => login, 'password' => password, 'host' => host, 'scheme' => 'http', 'path' => '')
|
178
183
|
end
|
179
184
|
|
180
185
|
it "should build remote url (with some help)" do
|
181
186
|
@options[:host] = "127.0.0.1"
|
182
|
-
parser_expects_uri_hash_to_url_with(@options[:login], @options[:password], "#{@options[:host]}:#{Db.default_server_port}")
|
187
|
+
parser_expects_uri_hash_to_url_with(@options[:login], @options[:password], "#{@options[:host]}:#{Db.default_server_port}").returns("remote_url/")
|
183
188
|
|
184
189
|
Db.remote_url(@options)
|
185
190
|
end
|
@@ -187,7 +192,7 @@ module CapTaffy
|
|
187
192
|
it "should build remote url with different port" do
|
188
193
|
@options[:host] = "127.0.0.1"
|
189
194
|
@options[:port] = 1234
|
190
|
-
parser_expects_uri_hash_to_url_with(@options[:login], @options[:password], "#{@options[:host]}:#{@options[:port]}")
|
195
|
+
parser_expects_uri_hash_to_url_with(@options[:login], @options[:password], "#{@options[:host]}:#{@options[:port]}").returns("remote_url")
|
191
196
|
|
192
197
|
Db.remote_url(@options)
|
193
198
|
end
|
@@ -199,15 +204,15 @@ module CapTaffy
|
|
199
204
|
Db.remote_url(@options).should == "remote_url"
|
200
205
|
end
|
201
206
|
|
202
|
-
|
203
|
-
|
207
|
+
running_db_it "should run with capistrano" do
|
208
|
+
capistrano_run_with(Db.server_command(@options))
|
204
209
|
|
205
210
|
Db.run(@capistrano, @options)
|
206
211
|
end
|
207
212
|
|
208
|
-
|
213
|
+
running_db_it "should do something to taps client" do
|
209
214
|
channel, stream, data = simulating_run_loop_with :data => ">> Listening on 0.0.0.0:5000, CTRL+C to stop\r\n" do
|
210
|
-
|
215
|
+
capistrano_run_with(Db.server_command(@options))
|
211
216
|
end
|
212
217
|
channel.expects(:close)
|
213
218
|
|
@@ -220,11 +225,11 @@ module CapTaffy
|
|
220
225
|
channel[:status].should == 0
|
221
226
|
end
|
222
227
|
|
223
|
-
|
228
|
+
running_db_it "should run taffy on different port" do
|
224
229
|
@options[:port] = 1234
|
225
230
|
|
226
231
|
channel, stream, data = simulating_run_loop_with :data => ">> Listening on 0.0.0.0:1234, CTRL+C to stop\r\n" do
|
227
|
-
|
232
|
+
capistrano_run_with(Db.server_command(@options))
|
228
233
|
end
|
229
234
|
channel.expects(:close)
|
230
235
|
Db.expects(:remote_url).with(@options.merge(:host => channel[:host])).returns("remote_url")
|
@@ -237,9 +242,9 @@ module CapTaffy
|
|
237
242
|
channel[:status].should == 0
|
238
243
|
end
|
239
244
|
|
240
|
-
|
245
|
+
running_db_it "should not do anything until taps sinatra server is running" do
|
241
246
|
simulating_run_loop_with :data => "asdfasdf" do
|
242
|
-
|
247
|
+
capistrano_run_with(Db.server_command(@options))
|
243
248
|
end
|
244
249
|
|
245
250
|
client = mock()
|
@@ -250,7 +255,7 @@ module CapTaffy
|
|
250
255
|
end
|
251
256
|
|
252
257
|
channel, stream, data = simulating_run_loop_with :data => ">> Listening on 0.0.0.0:5000, CTRL+C to stop\r\n" do
|
253
|
-
|
258
|
+
capistrano_run_with(Db.server_command(@options))
|
254
259
|
end
|
255
260
|
channel.expects(:close)
|
256
261
|
Db.expects(:remote_url).with(@options.merge(:host => channel[:host], :port => 5000)).returns("remote_url")
|
@@ -263,9 +268,9 @@ module CapTaffy
|
|
263
268
|
channel[:status].should == 0
|
264
269
|
end
|
265
270
|
|
266
|
-
|
271
|
+
running_db_it "should force 127.0.0.1 (local) for remote url" do
|
267
272
|
channel, stream, data = simulating_run_loop_with :data => ">> Listening on 0.0.0.0:5000, CTRL+C to stop\r\n" do
|
268
|
-
|
273
|
+
capistrano_run_with(Db.server_command(@options))
|
269
274
|
end
|
270
275
|
channel.expects(:close)
|
271
276
|
|
@@ -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/cap-taffy_spec.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib cap-taffy]))
|
2
2
|
|
3
|
-
module Capistrano
|
3
|
+
module Capistrano # :nodoc:
|
4
4
|
end
|
5
5
|
|
6
6
|
Spec::Runner.configure do |config|
|
@@ -34,12 +34,19 @@ Capistrano::Configuration.class_eval do
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
class String # :nodoc:
|
38
|
+
def demodulize
|
39
|
+
gsub(/^.*::/, '')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
37
43
|
module CapistranoHelpers
|
38
|
-
def self.included(base)
|
44
|
+
def self.included(base) # :nodoc:
|
39
45
|
base.extend CapistranoHelpers::ClassMethods
|
40
46
|
end
|
41
47
|
|
42
|
-
|
48
|
+
# Stubs the Capistrano Logger and yields to the block
|
49
|
+
def with_logger(&blk) # :yields:
|
43
50
|
logger_class = Class.new
|
44
51
|
logger = mock()
|
45
52
|
logger.stub_everything
|
@@ -50,41 +57,83 @@ module CapistranoHelpers
|
|
50
57
|
Capistrano.send(:remove_const, "Logger") rescue nil
|
51
58
|
end
|
52
59
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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])
|
57
65
|
end
|
58
66
|
|
59
|
-
|
60
|
-
|
61
|
-
@
|
62
|
-
@namespace_db.instance_variable_set(:@local_database_url, options[:local_database_url])
|
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)
|
63
70
|
end
|
64
71
|
|
65
|
-
|
66
|
-
|
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)
|
67
75
|
end
|
68
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
|
69
94
|
|
70
95
|
module ClassMethods
|
71
|
-
|
72
|
-
|
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
|
+
|
73
116
|
context ":#{task_name.to_s} task" do
|
74
117
|
before do
|
75
|
-
@
|
76
|
-
@
|
77
|
-
@
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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)
|
82
130
|
end
|
83
131
|
|
84
|
-
it
|
132
|
+
it description, &block
|
85
133
|
|
86
134
|
after do
|
87
|
-
|
135
|
+
const_name = @mod.to_s.demodulize
|
136
|
+
CapTaffy.send(:remove_const, const_name)
|
88
137
|
end
|
89
138
|
end
|
90
139
|
end
|
@@ -92,10 +141,14 @@ module CapistranoHelpers
|
|
92
141
|
end
|
93
142
|
|
94
143
|
module TaffyHelpers
|
95
|
-
def self.included(base)
|
144
|
+
def self.included(base) # :nodoc:
|
96
145
|
base.extend TaffyHelpers::ClassMethods
|
97
146
|
end
|
98
147
|
|
148
|
+
# A simple helper for mocking a quick object
|
149
|
+
#
|
150
|
+
# Usage:
|
151
|
+
# taps_client_who(:expects, :do_something)
|
99
152
|
def taps_client_who(method_symbol, *args)
|
100
153
|
client = mock()
|
101
154
|
client.send(method_symbol, *args)
|
@@ -103,24 +156,16 @@ module TaffyHelpers
|
|
103
156
|
end
|
104
157
|
|
105
158
|
module ClassMethods
|
106
|
-
|
107
|
-
|
159
|
+
# A wrapper for running CapTaffy::Db::run
|
160
|
+
def running_db_it(message, &blk)
|
161
|
+
context "when running db" do
|
108
162
|
before do
|
109
163
|
@capistrano = mock()
|
110
164
|
end
|
111
165
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
# invokes one loop of block, passing in channel, stream, data as arguments
|
117
|
-
def simulating_run_loop_with(options={}, &blk)
|
118
|
-
channel = options[:channel] || {:host => "192.168.1.20"}
|
119
|
-
stream = options[:stream]
|
120
|
-
data = options[:data]
|
121
|
-
|
122
|
-
blk.call.then.yields(channel, stream, data)
|
123
|
-
[channel, stream, data]
|
166
|
+
# See CapistranoHelpers
|
167
|
+
def capistrano_run_with(*args)
|
168
|
+
run_with(@capistrano, *args)
|
124
169
|
end
|
125
170
|
|
126
171
|
it message, &blk
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hsume2-cap-taffy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henry Hsu
|
@@ -52,6 +52,16 @@ dependencies:
|
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: 2.5.1
|
54
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:
|
55
65
|
description: Capistrano recipes for deploying databases and other common tasks.
|
56
66
|
email: henry@qlane.com
|
57
67
|
executables: []
|
@@ -68,13 +78,16 @@ files:
|
|
68
78
|
- lib/cap-taffy.rb
|
69
79
|
- lib/cap-taffy/db.rb
|
70
80
|
- lib/cap-taffy/parse.rb
|
81
|
+
- lib/cap-taffy/ssh.rb
|
71
82
|
- spec/cap-taffy/db_spec.rb
|
72
83
|
- spec/cap-taffy/parse_spec.rb
|
84
|
+
- spec/cap-taffy/ssh_spec.rb
|
73
85
|
- spec/cap-taffy_spec.rb
|
74
86
|
- spec/spec.opts
|
75
87
|
- spec/spec_helper.rb
|
76
88
|
has_rdoc: false
|
77
89
|
homepage: http://by.qlane.com
|
90
|
+
licenses:
|
78
91
|
post_install_message:
|
79
92
|
rdoc_options:
|
80
93
|
- --main
|
@@ -96,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
109
|
requirements: []
|
97
110
|
|
98
111
|
rubyforge_project: cap-taffy
|
99
|
-
rubygems_version: 1.
|
112
|
+
rubygems_version: 1.3.5
|
100
113
|
signing_key:
|
101
114
|
specification_version: 3
|
102
115
|
summary: Capistrano recipes for deploying databases (managing database.yml, importing/exporting/transfering databases, etc.)
|