webbynode 1.0.5.beta5 → 1.0.5.beta6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of webbynode might be problematic. Click here for more details.

data/Gemfile CHANGED
@@ -5,7 +5,7 @@ gem 'domainatrix'
5
5
  gem 'net-ssh'
6
6
  gem 'highline'
7
7
  gem 'rainbow'
8
- gem 'taps'
8
+ gem 'taps', '0.3.23'
9
9
 
10
10
  group :development do
11
11
  gem 'rspec'
data/Gemfile.lock CHANGED
@@ -12,12 +12,12 @@ GEM
12
12
  diff-lcs (1.1.2)
13
13
  domainatrix (0.0.7)
14
14
  addressable
15
- echoe (4.5.1)
15
+ echoe (4.5.6)
16
16
  allison
17
17
  gemcutter
18
18
  rubyforge
19
19
  fakeweb (1.3.0)
20
- gemcutter (0.6.1)
20
+ gemcutter (0.7.0)
21
21
  growl (1.0.3)
22
22
  guard (0.3.0)
23
23
  open_gem (~> 1.4.2)
@@ -27,7 +27,7 @@ GEM
27
27
  highline (1.6.1)
28
28
  httparty (0.7.4)
29
29
  crack (= 0.1.8)
30
- json_pure (1.5.1)
30
+ json_pure (1.5.3)
31
31
  launchy (0.3.7)
32
32
  configuration (>= 0.0.5)
33
33
  rake (>= 0.8.1)
@@ -35,12 +35,12 @@ GEM
35
35
  net-ssh (2.1.0)
36
36
  open_gem (1.4.2)
37
37
  launchy (~> 0.3.5)
38
- rack (1.2.1)
38
+ rack (1.3.2)
39
39
  rainbow (1.1.1)
40
40
  rake (0.8.7)
41
41
  rb-fsevent (0.4.0)
42
42
  rcov (0.9.9)
43
- rest-client (1.6.1)
43
+ rest-client (1.6.3)
44
44
  mime-types (>= 1.16)
45
45
  rspec (2.5.0)
46
46
  rspec-core (~> 2.5.0)
@@ -55,12 +55,12 @@ GEM
55
55
  sequel (3.20.0)
56
56
  sinatra (1.0)
57
57
  rack (>= 1.0)
58
- sqlite3 (1.3.3)
58
+ sqlite3 (1.3.4)
59
59
  sqlite3-ruby (1.3.3)
60
60
  sqlite3 (>= 1.3.3)
61
- taps (0.3.19)
61
+ taps (0.3.23)
62
62
  rack (>= 1.0.1)
63
- rest-client (< 1.7.0, >= 1.4.0)
63
+ rest-client (>= 1.4.0, < 1.7.0)
64
64
  sequel (~> 3.20.0)
65
65
  sinatra (~> 1.0.0)
66
66
  sqlite3-ruby (~> 1.2)
@@ -87,4 +87,4 @@ DEPENDENCIES
87
87
  rb-fsevent
88
88
  rcov
89
89
  rspec
90
- taps
90
+ taps (= 0.3.23)
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require 'rake/testtask'
4
4
 
5
5
  require 'echoe'
6
6
 
7
- Echoe.new('webbynode', '1.0.5.beta5') do |p|
7
+ Echoe.new('webbynode', '1.0.5.beta6') do |p|
8
8
  p.description = "Webbynode Deployment Gem"
9
9
  p.url = "http://webbynode.com"
10
10
  p.author = "Felipe Coury"
@@ -46,6 +46,24 @@ wn guides
46
46
  "
47
47
  end
48
48
 
49
+ require 'rspec/core/rake_task'
50
+
51
+ desc 'Default: run specs.'
52
+ task :default => :spec
53
+
54
+ desc "Run specs"
55
+ RSpec::Core::RakeTask.new do |t|
56
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
57
+ # Put spec opts in a file named .rspec in root
58
+ end
59
+
60
+ desc "Generate code coverage"
61
+ RSpec::Core::RakeTask.new(:coverage) do |t|
62
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
63
+ t.rcov = true
64
+ t.rcov_opts = ['--exclude', 'spec']
65
+ end
66
+
49
67
  require 'rcov/rcovtask'
50
68
  desc 'Measures test coverage using rcov'
51
69
  namespace :rcov do
@@ -17,7 +17,7 @@ module Webbynode::Commands
17
17
  handle_dns param(:dns_entry)
18
18
 
19
19
  app_name = io.app_name
20
- io.create_file(".pushand", "#! /bin/bash\nphd $0 #{app_name} #{param(:dns_entry)}\n", true)
20
+ pushand.create!(app_name, param(:dns_entry))
21
21
 
22
22
  git.add ".pushand"
23
23
  git.add ".webbynode/settings" if io.file_exists?(".webbynode/settings")
@@ -29,6 +29,9 @@ module Webbynode::Commands
29
29
  end
30
30
 
31
31
  io.log "Your application will start responding to #{param(:dns_entry)} after next deployment."
32
+ rescue Webbynode::ApiClient::InactiveZone
33
+ io.log "Domain #{$!.message.color(:yellow)} already setup on Webbynode DNS, but it's inactive."
34
+ io.log "Please reactivate it and try again."
32
35
  end
33
36
  end
34
37
  end
@@ -3,13 +3,17 @@ module Webbynode::Commands
3
3
  summary "Manages your application database"
4
4
 
5
5
  add_alias "db"
6
- allowed_actions %w(pull push)
6
+ allowed_actions %w(pull push config)
7
7
 
8
8
  option :debug, "Show server communication steps"
9
9
 
10
10
  requires_initialization!
11
11
  attr_reader :db
12
12
 
13
+ def default
14
+ io.log "Missing action: use #{"pull".color(:yellow)}, #{"push".color(:yellow)} or #{"config".color(:yellow)}. For more help use #{"#{File.basename $0} help database".color(:yellow)}."
15
+ end
16
+
13
17
  def pull
14
18
  go :pull
15
19
  end
@@ -18,6 +22,10 @@ module Webbynode::Commands
18
22
  go :push
19
23
  end
20
24
 
25
+ def config
26
+ ask_db_credentials(true)
27
+ end
28
+
21
29
  def go(action)
22
30
  ask_db_credentials
23
31
 
@@ -31,19 +39,38 @@ module Webbynode::Commands
31
39
  io.log ""
32
40
  io.log "Retrieving contents from #{db_name} database in #{ip}..."
33
41
  end
34
-
42
+
35
43
  taps = Webbynode::Taps.new(db_name, password, io, remote_executor)
36
44
  taps.debug = option(:debug)
37
45
  begin
46
+ io.log "Checking for dependencies..." if option(:debug)
47
+ taps.ensure_gems!
48
+
38
49
  io.log "Starting taps in server mode..." if option(:debug)
39
50
  taps.start
51
+
40
52
  io.log "Waiting for taps to start..." if option(:debug)
41
53
  sleep 4
54
+
42
55
  io.log "Sending action #{action} with db #{db[:name]}..." if option(:debug)
43
56
  taps.send(action, :user => db[:user],
44
57
  :password => db[:password],
45
58
  :database => db[:name],
46
59
  :remote_ip => ip)
60
+ rescue TapsError
61
+ if $!.message =~ /LoadError: no such file to load -- (.*)/
62
+ io.log "#{"ERROR:".color(:red)} Missing database adapter. You need to install #{$1.color(:yellow)} gem to handle your database."
63
+ elsif $!.message =~ /Mysql::Error: Unknown database '(.*)'/
64
+ io.log "#{"ERROR:".color(:red)} Unknown database #{$1.color(:yellow)}. Create the local database and try again."
65
+ elsif $!.message =~ /Sequel::DatabaseConnectionError -\> Mysql::Error: (.*)/
66
+ io.log "#{"ERROR:".color(:red)} Invalid MySQL credentials for your local database (#{$1})"
67
+ else
68
+ if $!.message =~ /(.*) -\> (.*)/
69
+ io.log "#{"ERROR:".color(:red)} Unexpected error - #{$2}"
70
+ else
71
+ io.log "#{"ERROR:".color(:red)} Unexpected error - #{$!.message}"
72
+ end
73
+ end
47
74
  ensure
48
75
  io.log "Stopping taps server..."
49
76
  taps.finish
@@ -64,16 +91,16 @@ module Webbynode::Commands
64
91
  }
65
92
  end
66
93
 
67
- def ask_db_credentials
94
+ def ask_db_credentials(force=false)
68
95
  retrieve_db_credentials
69
96
 
70
- unless db[:name]
71
- db[:name] = query("Database name", io.db_name)
72
- db[:user] = query(" User name", io.db_name)
97
+ if force || db[:name].nil?
98
+ db[:name] = query("Database name", db[:name] || io.db_name)
99
+ db[:user] = query(" User name", db[:user] || io.db_name)
73
100
  end
74
101
 
75
102
  save_password = false
76
- unless db[:password]
103
+ if force || db[:password].nil?
77
104
  db[:password] = query(" Password", "")
78
105
  save_password = ask("Save password (y/n)? ").downcase == 'y'
79
106
  end
@@ -5,7 +5,9 @@ module Webbynode::Commands
5
5
  option :dns, String, "The DNS used for this application"
6
6
  option :adddns, "Creates the DNS entries for the domain"
7
7
  option :port, "Specifies an alternate SSH port to connect to Webby", :validate => :integer
8
- option :engine, "Sets the application engine for the app", :validate => { :in => ['php', 'rack', 'rails', 'rails3'] }
8
+ option :engine, "Sets the application engine for the app", :validate => {
9
+ :in => ['php', 'rack', 'rails', 'rails3', 'html', 'wsgi', 'django', 'nodejs']
10
+ }
9
11
  option :trial, "Initializes this app for Rapp Trial"
10
12
 
11
13
  def execute
@@ -69,6 +71,13 @@ module Webbynode::Commands
69
71
  io.log "Application already initialized.".color(:red)
70
72
  end
71
73
 
74
+ def create_pushand
75
+ return if pushand_exists? && !@overwrite
76
+
77
+ io.log ""
78
+ pushand.create!(@app_name, @dns_entry)
79
+ end
80
+
72
81
  private
73
82
 
74
83
  def check_git_clean
@@ -114,13 +123,6 @@ module Webbynode::Commands
114
123
  io.file_exists?(".pushand")
115
124
  end
116
125
 
117
- def create_pushand
118
- return if pushand_exists? && !@overwrite
119
-
120
- io.log ""
121
- io.create_file(".pushand", "#! /bin/bash\nphd $0 #{@app_name} #{@dns_entry}\n", true)
122
- end
123
-
124
126
  def create_git_commit
125
127
  io.log "Initializing git and applying initial commit..."
126
128
  git.init
data/lib/webbynode/io.rb CHANGED
@@ -70,6 +70,7 @@ module Webbynode
70
70
  end
71
71
 
72
72
  def mkdir(path)
73
+ raise "Tried to create real directory: #{path}" if $testing
73
74
  # TODO: raise "Tried to create real folder: #{path}" if $testing
74
75
  FileUtils.mkdir_p(path)
75
76
  end
@@ -190,6 +191,7 @@ module Webbynode
190
191
  end
191
192
 
192
193
  def create_file(file_name, contents, executable=nil)
194
+ raise "Tried to create real file: #{file_name}" if $testing and !$testing_io
193
195
  File.open(file_name, "w") do |file|
194
196
  file.write(contents)
195
197
  end
@@ -16,5 +16,9 @@ module Webbynode
16
16
  def remote_db_name
17
17
  parse_remote_app_name.gsub(/[-._]/, "")
18
18
  end
19
+
20
+ def create!(app_name, dns_entry)
21
+ io.create_file(".pushand", "#! /bin/bash\nphd $0 #{app_name} #{dns_entry}\n", true)
22
+ end
19
23
  end
20
24
  end
@@ -16,6 +16,18 @@ module Webbynode
16
16
  ssh.execute "mkdir -p #{folder}"
17
17
  end
18
18
 
19
+ def version(v)
20
+ v ? "-v=#{v} " : ""
21
+ end
22
+
23
+ def gem_installed?(gem_name, version=nil)
24
+ exec("gem list -i #{version(version)}#{gem_name}") == 'true'
25
+ end
26
+
27
+ def install_gem(gem_name, version=nil)
28
+ exec("sudo gem install #{version(version)}#{gem_name} > /dev/null 2>1; echo $?") == '0'
29
+ end
30
+
19
31
  def remote_home
20
32
  exec('pwd').strip
21
33
  end
data/lib/webbynode/ssh.rb CHANGED
@@ -68,14 +68,14 @@ module Webbynode
68
68
  end
69
69
 
70
70
  ch.env "PATH", "/usr/bin:/usr/local/bin:/opt/ruby-enterprise/bin"
71
- ch.exec "cd #{app_name}; rails console production" do |ch, success|
71
+ ch.exec "cd #{app_name} && rails console production" do |ch, success|
72
72
  abort "Could not connect to rails console" unless success
73
73
 
74
74
  ch.on_data do |ch, data|
75
75
  next if data.chomp == input.chomp || data.chomp == ''
76
76
  if data =~ /^irb(.*)[\>|\*] /
77
77
  prompt = ''
78
- data.each_with_index do |s, i|
78
+ data.chars.each_with_index do |s, i|
79
79
  if s =~ /^irb(.*)[\>|\*] /
80
80
  prompt = s
81
81
  else
@@ -108,6 +108,7 @@ module Webbynode
108
108
  puts ""
109
109
  puts "Console done."
110
110
  rescue Exception => e
111
+ puts "Error: #{$!.message}"
111
112
  end
112
113
  end
113
114
 
@@ -1,5 +1,6 @@
1
1
  require 'taps/operation'
2
2
  require 'taps/cli'
3
+ require 'cgi'
3
4
 
4
5
  module Webbynode
5
6
  class Taps
@@ -17,6 +18,22 @@ module Webbynode
17
18
  @remote_executor = remote_executor
18
19
  end
19
20
 
21
+ def ensure_gems!
22
+ check_and_install ['taps', '0.3.23'], 'mysql'
23
+ end
24
+
25
+ def check_and_install(*gems)
26
+ gems.each do |g|
27
+ if g.is_a?(Array)
28
+ gemd = *g
29
+ else
30
+ gemd = [g]
31
+ end
32
+
33
+ remote_executor.install_gem *gemd unless remote_executor.gem_installed?(*gemd)
34
+ end
35
+ end
36
+
20
37
  def start
21
38
  @user = io.random_password
22
39
  @password = io.random_password
@@ -61,14 +78,24 @@ module Webbynode
61
78
  def execute(action, options)
62
79
  raise "Taps server was not started" unless user
63
80
 
64
- local_url = "mysql://#{options[:user]}:#{options[:password]}@localhost/#{options[:database]}"
65
- remote_url = "http://#{user}:#{password}@#{options[:remote_ip]}:5000"
66
-
67
- io.log "Running taps #{action}"
81
+ local_url = "mysql://#{options[:user]}:#{CGI.escape(options[:password])}@localhost/#{options[:database]}"
82
+ remote_url = "http://#{user}:#{CGI.escape(password)}@#{options[:remote_ip]}:5000"
68
83
 
69
84
  ::Taps::Cli.new([]).clientxfer(action.to_sym,
70
85
  :database_url => local_url,
71
86
  :remote_url => remote_url)
72
87
  end
73
88
  end
89
+ end
90
+
91
+ class TapsError < StandardError; end
92
+
93
+ class Taps::Config
94
+ def self.puts(error)
95
+ @error = error
96
+ end
97
+
98
+ def self.exit(num)
99
+ raise TapsError, @error
100
+ end
74
101
  end
data/lib/webbynode.rb CHANGED
@@ -77,7 +77,7 @@ require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'database')
77
77
  require File.join(File.dirname(__FILE__), 'webbynode', 'application')
78
78
 
79
79
  module Webbynode
80
- VERSION = '1.0.5.beta5'
80
+ VERSION = '1.0.5.beta6'
81
81
  end
82
82
 
83
83
  class Array
@@ -93,6 +93,14 @@ class Array
93
93
  end
94
94
  end
95
95
 
96
+ unless Object.respond_to?(:blank?)
97
+ class Object
98
+ def blank?
99
+ respond_to?(:empty?) ? empty? : !self
100
+ end
101
+ end
102
+ end
103
+
96
104
  class Net::HTTP
97
105
  alias_method :old_initialize, :initialize
98
106
  def initialize(*args)
@@ -6,25 +6,37 @@ describe Webbynode::Commands::ChangeDns do
6
6
  let(:io) { double("io").as_null_object }
7
7
  let(:api) { double("api").as_null_object }
8
8
  let(:cmd) { Webbynode::Commands::ChangeDns.new("the.newdns.com") }
9
-
9
+ let(:pushand) { stub.as_null_object }
10
10
 
11
11
  before(:each) do
12
12
  FakeWeb.clean_registry
13
13
  cmd.should_receive(:io).any_number_of_times.and_return(io)
14
14
  cmd.should_receive(:git).any_number_of_times.and_return(git)
15
15
  cmd.should_receive(:api).any_number_of_times.and_return(api)
16
-
16
+ cmd.stub(:pushand).and_return(pushand)
17
17
  end
18
18
 
19
19
  it "should change pushand" do
20
20
  io.should_receive(:app_name).and_return("myapp")
21
- io.should_receive(:create_file).with(".pushand", "#! /bin/bash\nphd $0 myapp the.newdns.com\n", true)
21
+ pushand.should_receive(:create!).with("myapp", "the.newdns.com")
22
22
  git.should_receive(:parse_remote_ip).and_return("1.2.3.4")
23
23
  api.should_receive(:create_record).with("the.newdns.com", "1.2.3.4")
24
24
 
25
25
  cmd.run
26
26
  end
27
27
 
28
+ it "gives an error message when zone exists, but is inactive" do
29
+ git.should_receive(:parse_remote_ip).and_return("1.2.3.4")
30
+ api.should_receive(:create_record).with("the.newdns.com", "1.2.3.4").and_raise(Webbynode::ApiClient::InactiveZone.new( "the.newdns.com"))
31
+
32
+ io.should_receive(:log).with("Changing DNS to the.newdns.com...", :quiet_start)
33
+ io.should_receive(:log).with("Creating DNS entry for the.newdns.com...")
34
+ io.should_receive(:log).with("Domain the.newdns.com already setup on Webbynode DNS, but it's inactive.")
35
+ io.should_receive(:log).with("Please reactivate it and try again.")
36
+
37
+ cmd.run
38
+ end
39
+
28
40
  it "should give an error message if there are git changes pending" do
29
41
  git.should_receive(:clean?).and_return(false)
30
42
 
@@ -6,6 +6,7 @@ describe Webbynode::Commands::Console do
6
6
  let(:re) { double("RemoteExecutor").as_null_object }
7
7
  let(:git) { double("Git").as_null_object }
8
8
  let(:server) { double("Server").as_null_object }
9
+ let(:pushand) { stub.as_null_object }
9
10
 
10
11
  subject do
11
12
  Webbynode::Commands::Console.new.tap do |cmd|
@@ -13,6 +14,7 @@ describe Webbynode::Commands::Console do
13
14
  cmd.stub!(:remote_executor).and_return(re)
14
15
  cmd.stub!(:server).and_return(server)
15
16
  cmd.stub!(:git).and_return(git)
17
+ cmd.stub(:pushand).and_return(pushand)
16
18
  end
17
19
  end
18
20
 
@@ -2,10 +2,11 @@
2
2
  require File.join(File.expand_path(File.dirname(__FILE__)), '../..', 'spec_helper')
3
3
 
4
4
  describe Webbynode::Commands::Database do
5
- let(:io) { double("io").as_null_object }
6
- let(:re) { double("re").as_null_object }
7
- let(:git) { double("git").as_null_object }
8
- let(:pa) { double("pushand").as_null_object }
5
+ let(:io) { double("io").as_null_object }
6
+ let(:re) { double("re").as_null_object }
7
+ let(:git) { double("git").as_null_object }
8
+ let(:pa) { double("pushand").as_null_object }
9
+ let(:taps) { double("taps").as_null_object }
9
10
 
10
11
  def prepare(*params)
11
12
  Webbynode::Commands::Database.new(*params).tap do |a|
@@ -16,9 +17,86 @@ describe Webbynode::Commands::Database do
16
17
  end
17
18
  end
18
19
 
20
+ describe '#config' do
21
+ subject { prepare "config" }
22
+
23
+ it 'asks for credentials again, even if already provided' do
24
+ io.should_receive(:load_setting).with("database_name").and_return("dbname")
25
+ io.should_receive(:load_setting).with("database_user").and_return("username")
26
+ io.should_receive(:load_setting).with("database_password").and_return("dbpassword")
27
+
28
+ io.should_receive(:db_name).any_number_of_times.and_return("myapp")
29
+
30
+ subject.should_receive(:ask).with("Database name [dbname]: ").and_return("")
31
+ subject.should_receive(:ask).with(" User name [username]: ").and_return("")
32
+ subject.should_receive(:ask).with(" Password []: ").and_return("")
33
+ subject.should_receive(:ask).with("Save password (y/n)? ").and_return("y")
34
+
35
+ subject.execute
36
+ end
37
+ end
38
+
19
39
  describe '#pull' do
20
40
  subject { prepare "pull" }
21
41
 
42
+ it "installs taps and mysql when none installed" do
43
+ Webbynode::Taps.should_receive(:new).and_return(taps)
44
+
45
+ subject.stub(:ask_db_credentials)
46
+ subject.stub(:db).and_return({ :name => 'db_name' })
47
+ subject.stub(:sleep)
48
+ taps.stub(:start)
49
+ taps.should_receive(:ensure_gems!)
50
+
51
+ subject.execute
52
+ end
53
+
54
+ context 'db failures' do
55
+ def prepare_with_error(error)
56
+ subject.stub(:ask_db_credentials)
57
+ subject.stub(:db).and_return({ :name => 'db_name' })
58
+ subject.stub(:sleep)
59
+
60
+ Webbynode::Taps.should_receive(:new).and_return(taps)
61
+
62
+ pa.should_receive(:remote_db_name).and_return('db_name')
63
+ re.should_receive(:retrieve_db_password).and_return('password')
64
+ git.should_receive(:parse_remote_ip).and_return('1.2.3.4')
65
+
66
+ taps.should_receive(:pull).and_raise(TapsError.new(error))
67
+ end
68
+
69
+ it "shows an user friendly error message cannot connect to local database" do
70
+ prepare_with_error "Failed to connect to database:
71
+ Sequel::DatabaseConnectionError -> Mysql::Error: Access denied for user 'root'@'localhost' (using password: YES)"
72
+ io.should_receive(:log).with("ERROR: Invalid MySQL credentials for your local database (Access denied for user 'root'@'localhost' (using password: YES))")
73
+
74
+ subject.execute
75
+ end
76
+
77
+ it "shows an user friendly error for URI errors" do
78
+ prepare_with_error "Failed to connect to database:
79
+ URI::InvalidURIError -> the scheme mysql does not accept registry part: root:P@ssw0rd@localhost (or bad hostname?)."
80
+ io.should_receive(:log).with("ERROR: Unexpected error - the scheme mysql does not accept registry part: root:P@ssw0rd@localhost (or bad hostname?).")
81
+
82
+ subject.execute
83
+ end
84
+
85
+ it "shows an user friendly error message when a MySQL database doesn't exist" do
86
+ prepare_with_error "Failed to connect to database:\n Sequel::DatabaseConnectionError -> Mysql::Error: Unknown database 'r3app'"
87
+ io.should_receive(:log).with("ERROR: Unknown database r3app. Create the local database and try again.")
88
+
89
+ subject.execute
90
+ end
91
+
92
+ it "shows an user friendly error message when an adapter is not present" do
93
+ prepare_with_error "Failed to connect to database:\n Sequel::AdapterNotFound -> LoadError: no such file to load -- mysql"
94
+ io.should_receive(:log).with("ERROR: Missing database adapter. You need to install mysql gem to handle your database.")
95
+
96
+ subject.execute
97
+ end
98
+ end
99
+
22
100
  it "asks the local database credentials and name" do
23
101
  io.should_receive(:load_setting).with("database_name").and_return(nil)
24
102
  io.should_receive(:load_setting).with("database_password").and_return(nil)