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.
@@ -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.)