hsume2-cap-taffy 1.0.0 → 1.0.1

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.
@@ -1,3 +1,7 @@
1
+ == 1.0.1 / 2009-09-14
2
+ * 1 minor enhancement
3
+ * Added authorizing SSH public keys on remote server(s).
4
+
1
5
  == 1.0.0 / 2009-09-14
2
6
 
3
7
  * 1 major enhancement
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
- Usage
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
- ### Managing `database.yml`
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'
@@ -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.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
@@ -2,7 +2,7 @@
2
2
  module CapTaffy
3
3
 
4
4
  # :stopdoc:
5
- VERSION = '1.0.0'
5
+ VERSION = '1.0.1'
6
6
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
7
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
8
  # :startdoc:
@@ -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
- def local(env)
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
- def remote(instance, env)
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
- def taps_client(local_database_url, remote_url, &blk)
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 <local_database_url> <login> <password> [--port=N]
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
- def run(instance, options = {} , &blk)
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; end
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
- def dry_run_safe(returning = nil, &block)
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.remote(self, 'production') }
105
- @local_database_url = dry_run_safe('') { CapTaffy::Db.local('development') }
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
@@ -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
- def initialize
38
+ # Override to do nothing on #new
39
+ def initialize # :nodoc:
37
40
 
38
41
  end
39
42
 
40
- # Do nothing
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; end
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
@@ -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
- @db_mod.expects(:remote).returns("remote_db_url")
27
- @db_mod.expects(:local).returns("local_db_url")
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
- @namespace_db.instance_variable_get(:@remote_database_url).should == "remote_db_url"
32
- @namespace_db.instance_variable_get(:@local_database_url).should == "local_db_url"
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
- for_task :push, :roles => :app, :it => "should send taps client cmd_send" do
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
- @namespace_db.expects(:detect)
43
+ @namespace.expects(:detect)
38
44
  namespace_with_variables(:taps_port => nil)
39
- namespace_with_expected_options(options)
40
-
41
- @db_mod.expects(:tmp_pass).with(@namespace_db.fetch(:user)).returns(options[:password])
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
- load_taffy
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
- @namespace_db.expects(:detect)
54
+ @namespace.expects(:detect)
50
55
  namespace_with_variables(:taps_port => 1234)
51
- namespace_with_expected_options(options)
52
- @db_mod.expects(:tmp_pass).with(@namespace_db.fetch(:user)).returns(options[:password])
53
- @db_mod.expects(:run).with(@namespace_db, options)
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
- load_taffy
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
- @namespace_db.expects(:detect)
65
+ @namespace.expects(:detect)
61
66
  namespace_with_variables(:taps_port => 1234, :local => true)
62
- namespace_with_expected_options(options)
63
- @db_mod.expects(:tmp_pass).with(@namespace_db.fetch(:user)).returns(options[:password])
64
- @db_mod.expects(:run).with(@namespace_db, options.merge(: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))
65
70
 
66
- load_taffy
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
- @namespace_db.expects(:detect)
76
+ @namespace.expects(:detect)
72
77
  namespace_with_variables(:taps_port => nil)
73
- namespace_with_expected_options(options)
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
- @db_mod.expects(:tmp_pass).with(@namespace_db.fetch(:user)).returns(options[:password])
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.local(env)
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.remote(instance, env)
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' => '').returns("remote_url")
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
- running_taffy_it "should run with capistrano" do
203
- run_capistrano_with(Db.server_command(@options))
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
- running_taffy_it "should do something to taps client" do
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
- run_capistrano_with(Db.server_command(@options))
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
- running_taffy_it "should run taffy on different port" do
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
- run_capistrano_with(Db.server_command(@options))
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
- running_taffy_it "should not do anything until taps sinatra server is running" do
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
- run_capistrano_with(Db.server_command(@options))
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
- run_capistrano_with(Db.server_command(@options))
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
- running_taffy_it "should force 127.0.0.1 (local) for remote url" do
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
- run_capistrano_with(Db.server_command(@options))
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
@@ -1,7 +1,4 @@
1
-
2
1
  require File.join(File.dirname(__FILE__), %w[spec_helper])
3
2
 
4
3
  describe CapTaffy do
5
4
  end
6
-
7
- # EOF
@@ -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
- def with_logger(&blk)
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
- def load_taffy
54
- with_logger do
55
- load 'lib/cap-taffy/db.rb'
56
- end
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
- def namespace_with_expected_options(options)
60
- @namespace_db.stubs(:fetch).with(:user).returns(options[:login])
61
- @namespace_db.instance_variable_set(:@remote_database_url, options[:remote_database_url])
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
- def namespace_with_variables(variables)
66
- @namespace_db.stubs(:variables).returns(variables)
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
- def for_task(task_name, options = {}, &block)
72
- message = options.delete(:it)
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
- @namespace_db = Capistrano::Configuration.instance.namespaces[:db] = mock()
76
- @namespace_db.stubs(:desc)
77
- @namespace_db.stubs(:task)
78
- @namespace_db.expects(:task).with(task_name, options).yields
79
-
80
- @db_mod = Module.new
81
- CapTaffy.const_set("Db", @db_mod)
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 message, &block
132
+ it description, &block
85
133
 
86
134
  after do
87
- CapTaffy.send(:remove_const, "Db")
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
- def running_taffy_it(message, &blk)
107
- context "when running taffy" do
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
- def run_capistrano_with(*args)
113
- @capistrano.expects(:run).with(*args)
114
- end
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.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.2.0
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.)