webbynode 0.1.2 → 0.2.0
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/Manifest +88 -4
- data/PostInstall.txt +42 -9
- data/README.rdoc +6 -0
- data/Rakefile +16 -19
- data/assets/webbynode.png +0 -0
- data/bin/webbynode +3 -3
- data/bin/wn +7 -0
- data/cucumber.yml +1 -0
- data/devver.rake +174 -0
- data/features/bootstrap.feature +17 -0
- data/features/step_definitions/command_steps.rb +14 -0
- data/features/support/env.rb +22 -0
- data/features/support/hooks.rb +8 -0
- data/{test/test_webbynode.rb → features/support/io_features.rb} +0 -0
- data/features/support/mocha.rb +15 -0
- data/lib/templates/api_token +14 -0
- data/lib/templates/backup +15 -0
- data/lib/templates/gitignore +4 -0
- data/lib/templates/help +53 -0
- data/lib/webbynode/api_client.rb +121 -0
- data/lib/webbynode/application.rb +21 -0
- data/lib/webbynode/command.rb +332 -0
- data/lib/webbynode/commands/add_backup.rb +33 -0
- data/lib/webbynode/commands/add_key.rb +24 -0
- data/lib/webbynode/commands/alias.rb +153 -0
- data/lib/webbynode/commands/change_dns.rb +29 -0
- data/lib/webbynode/commands/config.rb +15 -0
- data/lib/webbynode/commands/delete.rb +25 -0
- data/lib/webbynode/commands/help.rb +25 -0
- data/lib/webbynode/commands/init.rb +77 -0
- data/lib/webbynode/commands/push.rb +70 -0
- data/lib/webbynode/commands/remote.rb +28 -0
- data/lib/webbynode/commands/restart.rb +25 -0
- data/lib/webbynode/commands/start.rb +23 -0
- data/lib/webbynode/commands/stop.rb +23 -0
- data/lib/webbynode/commands/tasks.rb +149 -0
- data/lib/webbynode/commands/version.rb +8 -0
- data/lib/webbynode/commands/webbies.rb +30 -0
- data/lib/webbynode/git.rb +112 -0
- data/lib/webbynode/io.rb +175 -0
- data/lib/webbynode/notify.rb +19 -0
- data/lib/webbynode/option.rb +84 -0
- data/lib/webbynode/parameter.rb +27 -0
- data/lib/webbynode/properties.rb +43 -0
- data/lib/webbynode/push_and.rb +16 -0
- data/lib/webbynode/remote_executor.rb +21 -0
- data/lib/webbynode/server.rb +39 -0
- data/lib/webbynode/ssh.rb +65 -0
- data/lib/webbynode/ssh_keys.rb +36 -0
- data/lib/webbynode.rb +56 -0
- data/spec/fixtures/aliases +0 -0
- data/spec/fixtures/api/credentials +3 -0
- data/spec/fixtures/api/dns +30 -0
- data/spec/fixtures/api/dns_a_record +20 -0
- data/spec/fixtures/api/dns_a_record_already_exists +15 -0
- data/spec/fixtures/api/dns_a_record_error +13 -0
- data/spec/fixtures/api/dns_new_zone +17 -0
- data/spec/fixtures/api/webbies +26 -0
- data/spec/fixtures/api/webbies_unauthorized +12 -0
- data/spec/fixtures/api/webby +6 -0
- data/spec/fixtures/commands/tasks/after_push +3 -0
- data/spec/fixtures/fixture_helpers +2 -0
- data/spec/fixtures/git/config/210.11.13.12 +9 -0
- data/spec/fixtures/git/config/67.23.79.31 +9 -0
- data/spec/fixtures/git/config/67.23.79.32 +9 -0
- data/spec/fixtures/git/config/config +9 -0
- data/spec/fixtures/git/status/clean +2 -0
- data/spec/fixtures/git/status/dirty +9 -0
- data/spec/fixtures/pushand +2 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/webbynode/api_client_spec.rb +227 -0
- data/spec/webbynode/application_spec.rb +50 -0
- data/spec/webbynode/command_spec.rb +285 -0
- data/spec/webbynode/commands/add_backup_spec.rb +66 -0
- data/spec/webbynode/commands/add_key_spec.rb +58 -0
- data/spec/webbynode/commands/alias_spec.rb +213 -0
- data/spec/webbynode/commands/change_dns_spec.rb +51 -0
- data/spec/webbynode/commands/config_spec.rb +22 -0
- data/spec/webbynode/commands/delete_spec.rb +78 -0
- data/spec/webbynode/commands/help_spec.rb +43 -0
- data/spec/webbynode/commands/init_spec.rb +326 -0
- data/spec/webbynode/commands/push_spec.rb +175 -0
- data/spec/webbynode/commands/remote_spec.rb +76 -0
- data/spec/webbynode/commands/tasks_spec.rb +288 -0
- data/spec/webbynode/commands/version_spec.rb +18 -0
- data/spec/webbynode/commands/webbies_spec.rb +27 -0
- data/spec/webbynode/git_spec.rb +310 -0
- data/spec/webbynode/io_spec.rb +198 -0
- data/spec/webbynode/option_spec.rb +48 -0
- data/spec/webbynode/parameter_spec.rb +70 -0
- data/spec/webbynode/push_and_spec.rb +28 -0
- data/spec/webbynode/remote_executor_spec.rb +25 -0
- data/spec/webbynode/server_spec.rb +101 -0
- data/webbynode.gemspec +25 -16
- metadata +182 -14
- data/lib/wn.rb +0 -106
- data/test/test_helper.rb +0 -9
- data/test/test_wn.rb +0 -220
data/lib/webbynode/io.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Webbynode
|
4
|
+
class DirectoryNotFound < StandardError; end
|
5
|
+
|
6
|
+
class Io
|
7
|
+
class KeyAlreadyExists < StandardError; end
|
8
|
+
|
9
|
+
TemplatesPath = File.join(File.dirname(__FILE__), '..', 'templates')
|
10
|
+
|
11
|
+
def app_name
|
12
|
+
Dir.pwd.split("/").last.gsub(/[\.| ]/, "_")
|
13
|
+
end
|
14
|
+
|
15
|
+
def exec(s, redirect_stderr=true)
|
16
|
+
`#{s}#{redirect_stderr ? " 2>&1" : ""}`
|
17
|
+
end
|
18
|
+
|
19
|
+
def directory?(s)
|
20
|
+
File.directory?(s)
|
21
|
+
end
|
22
|
+
|
23
|
+
def file_exists?(f)
|
24
|
+
File.exists?(f)
|
25
|
+
end
|
26
|
+
|
27
|
+
def read_file(f)
|
28
|
+
File.read(f)
|
29
|
+
end
|
30
|
+
|
31
|
+
def open_file(f, a, &blk)
|
32
|
+
File.open(f, a, &blk)
|
33
|
+
end
|
34
|
+
|
35
|
+
def log(text, notify=false)
|
36
|
+
notify = :action unless notify
|
37
|
+
|
38
|
+
case notify
|
39
|
+
when :notify
|
40
|
+
notify = true
|
41
|
+
puts "#{text}"
|
42
|
+
|
43
|
+
when :start
|
44
|
+
notify = true
|
45
|
+
puts "[Webbynode] #{text}"
|
46
|
+
|
47
|
+
when :quiet_start
|
48
|
+
notify = false
|
49
|
+
puts "[Webbynode] #{text}"
|
50
|
+
|
51
|
+
when :action
|
52
|
+
notify = false
|
53
|
+
puts " #{text}"
|
54
|
+
|
55
|
+
when :warning
|
56
|
+
notify = false
|
57
|
+
puts " WARNING: #{text}"
|
58
|
+
|
59
|
+
when :action
|
60
|
+
notify = false
|
61
|
+
puts " #{text}"
|
62
|
+
|
63
|
+
when :finish
|
64
|
+
notify = true
|
65
|
+
puts
|
66
|
+
puts "[Webbynode] #{text}"
|
67
|
+
|
68
|
+
when :error
|
69
|
+
notify = true
|
70
|
+
puts
|
71
|
+
puts "[Webbynode] ERROR: #{text}"
|
72
|
+
|
73
|
+
else
|
74
|
+
notify = false
|
75
|
+
puts " #{text}"
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
Webbynode::Notify.message(text) if notify
|
80
|
+
end
|
81
|
+
|
82
|
+
def log_and_exit(text, notify=false)
|
83
|
+
log(text, notify)
|
84
|
+
exit
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_local_key(passphrase="")
|
88
|
+
unless File.exists?(Webbynode::Commands::AddKey::LocalSshKey)
|
89
|
+
exec "ssh-keygen -t rsa -N \"#{passphrase}\" -f #{Webbynode::Commands::AddKey::LocalSshKey}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_file(file_name, contents, executable=nil)
|
94
|
+
File.open(file_name, "w") do |file|
|
95
|
+
file.write(contents)
|
96
|
+
end
|
97
|
+
FileUtils.chmod 0755, file_name if executable
|
98
|
+
end
|
99
|
+
|
100
|
+
def delete_file(file_name)
|
101
|
+
File.delete(file_name)
|
102
|
+
end
|
103
|
+
|
104
|
+
def templates_path
|
105
|
+
TemplatesPath
|
106
|
+
end
|
107
|
+
|
108
|
+
def read_from_template(template)
|
109
|
+
read_file File.join(templates_path, template)
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_from_template(file, template)
|
113
|
+
contents = read_from_template(template)
|
114
|
+
create_file(file, contents)
|
115
|
+
end
|
116
|
+
|
117
|
+
def properties(s)
|
118
|
+
(@properties||={})[s] = Properties.new(s)
|
119
|
+
end
|
120
|
+
|
121
|
+
def general_settings
|
122
|
+
@general_settings ||= properties("#{ENV['HOME']}/.webbynode")
|
123
|
+
end
|
124
|
+
|
125
|
+
def with_settings_for(file, &blk)
|
126
|
+
settings = properties(file)
|
127
|
+
yield settings
|
128
|
+
settings.save
|
129
|
+
end
|
130
|
+
|
131
|
+
def with_general_settings(&blk)
|
132
|
+
yield general_settings
|
133
|
+
general_settings.save
|
134
|
+
end
|
135
|
+
|
136
|
+
def with_setting(&blk)
|
137
|
+
with_settings_for ".webbynode/settings", &blk
|
138
|
+
end
|
139
|
+
|
140
|
+
def add_general_setting(key, value)
|
141
|
+
with_general_settings { |s| s[key] = value }
|
142
|
+
end
|
143
|
+
|
144
|
+
def remove_setting(key)
|
145
|
+
with_setting { |s| s.remove key }
|
146
|
+
end
|
147
|
+
|
148
|
+
def add_setting(key, value)
|
149
|
+
with_setting { |s| s[key] = value }
|
150
|
+
end
|
151
|
+
|
152
|
+
def config_multi_add(key, new_value)
|
153
|
+
raise "Missing Webbynode config file" unless file_exists?(".webbynode/config")
|
154
|
+
config = read_yaml(".webbynode/config")
|
155
|
+
(config[key] ||= []) << new_value
|
156
|
+
write_yaml(config)
|
157
|
+
end
|
158
|
+
|
159
|
+
def read_yaml(file)
|
160
|
+
YAML.load_file(file)
|
161
|
+
end
|
162
|
+
|
163
|
+
def write_yaml(obj)
|
164
|
+
create_file(file, obj.to_yaml)
|
165
|
+
end
|
166
|
+
|
167
|
+
def read_config(config_file)
|
168
|
+
read_file(config_file).split("\n").inject({}) do |hash, line|
|
169
|
+
line_parts = line.split("=")
|
170
|
+
hash[line_parts.first.strip.to_sym] = line_parts.last.strip
|
171
|
+
hash
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Webbynode
|
2
|
+
class Notify
|
3
|
+
|
4
|
+
TITLE = "Webbynode"
|
5
|
+
IMAGE_PATH = File.join(File.dirname(__FILE__), '..', '..', 'assets', 'webbynode.png')
|
6
|
+
|
7
|
+
def self.message(message)
|
8
|
+
if self.installed? and !$testing
|
9
|
+
%x(growlnotify -t "#{TITLE}" -m "#{message}" --image "#{IMAGE_PATH}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.installed?
|
14
|
+
return false if %x(which growlnotify).chomp.empty?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Webbynode
|
2
|
+
class Option
|
3
|
+
attr_reader :name, :kind, :desc, :options, :errors
|
4
|
+
attr_accessor :value
|
5
|
+
|
6
|
+
def Option.name_for(s)
|
7
|
+
return $1 if s =~ /^--(\w+)(=("[^"]+"|[\w]+))*/
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
raise "Cannot initialize Parameter without a name" unless args.first
|
12
|
+
|
13
|
+
@errors = []
|
14
|
+
@value = nil
|
15
|
+
@options = args.pop if args.last.is_a?(Hash)
|
16
|
+
@options ||= {}
|
17
|
+
@options[:required] = true if @options[:required].nil?
|
18
|
+
|
19
|
+
@name = args[0]
|
20
|
+
if args[1].is_a?(String)
|
21
|
+
@desc = args[1]
|
22
|
+
else
|
23
|
+
@kind = args[1]
|
24
|
+
@desc = args[2]
|
25
|
+
end
|
26
|
+
|
27
|
+
@kind ||= String
|
28
|
+
@value = [] if @kind == Array
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate!
|
32
|
+
raise Webbynode::Command::InvalidCommand, errors.join("\n") unless valid?
|
33
|
+
end
|
34
|
+
|
35
|
+
def valid?
|
36
|
+
@errors = []
|
37
|
+
if (validations = @options[:validate])
|
38
|
+
validations.each_pair do |key, value|
|
39
|
+
@errors << send("#{key}_error", value) unless send(key, value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
@errors.empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
def in(allowed_values)
|
46
|
+
allowed_values.include?(self.value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def in_error(allowed_values)
|
50
|
+
opts = allowed_values.map { |v| "'#{v}'"}
|
51
|
+
"Invalid value '#{value}' for #{self.class.name.split("::").last.downcase} '#{self.name}'. It should be one of #{opts.to_phrase("or")}."
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse(s)
|
55
|
+
if s =~ /^--(\w+)(=("[^"]+"|[\w]+))*/
|
56
|
+
self.value = $3 ? $3.gsub(/"/, "") : true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def array?
|
61
|
+
kind == Array
|
62
|
+
end
|
63
|
+
|
64
|
+
def reset!
|
65
|
+
self.value = default_value
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_value
|
69
|
+
array? ? [] : nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def required?
|
73
|
+
@options[:required]
|
74
|
+
end
|
75
|
+
|
76
|
+
def take
|
77
|
+
@options[:take]
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s
|
81
|
+
"--#{name.to_s}#{take ? "=#{take.to_s}" : ""}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Webbynode
|
2
|
+
class Parameter < Webbynode::Option
|
3
|
+
def initialize(*args)
|
4
|
+
super
|
5
|
+
@options[:required] = true if @options[:required].nil?
|
6
|
+
end
|
7
|
+
|
8
|
+
def validate!
|
9
|
+
if required? and self.value === self.default_value
|
10
|
+
raise Webbynode::Command::InvalidCommand, "Missing '#{name}' parameter."
|
11
|
+
end
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def required?
|
16
|
+
@options[:required]
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
if required?
|
21
|
+
"#{name}"
|
22
|
+
else
|
23
|
+
"[#{name}]"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Webbynode
|
2
|
+
class Properties < Hash
|
3
|
+
attr_accessor :file, :properties
|
4
|
+
|
5
|
+
def io
|
6
|
+
@io ||= Webbynode::Io.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(file, create=true)
|
10
|
+
raise "Tried to instantiate a real property file" if $testing
|
11
|
+
@file = file
|
12
|
+
|
13
|
+
begin
|
14
|
+
IO.foreach(file) do |line|
|
15
|
+
self[$1.strip.to_s] = $2.strip if line = ~ /([^=]*)=(.*)\/\/(.*)/ || line =~ /([^=]*)=(.*)/
|
16
|
+
end
|
17
|
+
rescue
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
output = "File name #{@file}\n"
|
23
|
+
self.each { |key, value| output += " #{key} = #{value}\n" }
|
24
|
+
output
|
25
|
+
end
|
26
|
+
|
27
|
+
def add(key, value = nil)
|
28
|
+
return unless key.length > 0
|
29
|
+
self[key] = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def remove(key)
|
33
|
+
return unless key.length > 0
|
34
|
+
self.delete(key)
|
35
|
+
end
|
36
|
+
|
37
|
+
def save
|
38
|
+
file = File.new(@file, "w+")
|
39
|
+
self.each { |key, value| file.puts "#{key}=#{value}\n" }
|
40
|
+
file.close
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Webbynode
|
2
|
+
class PushAndError < StandardError; end
|
3
|
+
class PushAndFileNotFound < StandardError; end
|
4
|
+
|
5
|
+
class PushAnd
|
6
|
+
def io; @io ||= Webbynode::Io.new; end
|
7
|
+
|
8
|
+
def present?
|
9
|
+
io.file_exists?(".pushand")
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_remote_app_name
|
13
|
+
io.read_file(".pushand")[/^phd \$0 ([^ ]+)/, 1]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Webbynode
|
2
|
+
class RemoteExecutor
|
3
|
+
attr_accessor :ip
|
4
|
+
|
5
|
+
def initialize(ip)
|
6
|
+
@ip = ip
|
7
|
+
end
|
8
|
+
|
9
|
+
def ssh
|
10
|
+
@ssh ||= Ssh.new(ip)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_folder(folder)
|
14
|
+
ssh.execute "mkdir -p #{folder}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def exec(cmd, echo=false)
|
18
|
+
ssh.execute(cmd, echo)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Webbynode
|
2
|
+
class InvalidAuthentication < StandardError; end
|
3
|
+
class PermissionError < StandardError; end
|
4
|
+
class ApplicationNotDeployed < StandardError; end
|
5
|
+
|
6
|
+
class Server
|
7
|
+
attr_accessor :ip
|
8
|
+
|
9
|
+
def initialize(ip)
|
10
|
+
@ssh = Ssh.new(ip)
|
11
|
+
@ip = ip
|
12
|
+
end
|
13
|
+
|
14
|
+
def io
|
15
|
+
@io ||= Webbynode::Io.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def remote_executor
|
19
|
+
@remote_executor ||= Webbynode::RemoteExecutor.new(ip)
|
20
|
+
end
|
21
|
+
|
22
|
+
def pushand
|
23
|
+
@pushand ||= Webbynode::PushAnd.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_ssh_key(key_file, passphrase="")
|
27
|
+
io.create_local_key(key_file, passphrase) unless io.file_exists?(key_file)
|
28
|
+
remote_executor.create_folder("~/.ssh")
|
29
|
+
|
30
|
+
key_contents = io.read_file(key_file)
|
31
|
+
remote_executor.exec "echo \"#{key_contents}\" >> ~/.ssh/authorized_keys; chmod 644 ~/.ssh/authorized_keys"
|
32
|
+
end
|
33
|
+
|
34
|
+
def application_pushed?
|
35
|
+
return false if remote_executor.exec("cd #{pushand.parse_remote_app_name}") =~ /No such file or directory/
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Webbynode
|
2
|
+
class Ssh
|
3
|
+
def initialize(remote_ip)
|
4
|
+
@remote_ip = remote_ip
|
5
|
+
end
|
6
|
+
|
7
|
+
def io
|
8
|
+
@io ||= Webbynode::Io.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def connect
|
12
|
+
raise "No IP given" unless @remote_ip
|
13
|
+
@conn = nil if @conn and @conn.closed?
|
14
|
+
@conn ||= Net::SSH.start(@remote_ip, 'git', :auth_methods => %w(publickey hostbased))
|
15
|
+
rescue Net::SSH::AuthenticationFailed
|
16
|
+
HighLine.track_eof = false
|
17
|
+
|
18
|
+
begin
|
19
|
+
@password ||= ask("Enter your deployment password for #{@remote_ip}: ") { |q| q.echo = '' }
|
20
|
+
@conn ||= Net::SSH.start(@remote_ip, 'git', :password => @password)
|
21
|
+
rescue Net::SSH::AuthenticationFailed
|
22
|
+
io.log "Could not connect to server: invalid authentication.", true
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
|
26
|
+
rescue Net::SSH::Disconnect
|
27
|
+
io.log "Could not connect to the server: Wrong IP or Server Offline.", true
|
28
|
+
exit
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute(script, echo=false)
|
33
|
+
connect
|
34
|
+
output = ""
|
35
|
+
error_output = ""
|
36
|
+
|
37
|
+
exit_code = nil
|
38
|
+
channel = @conn.open_channel do |chan|
|
39
|
+
chan.on_request('exit-status') do |ch, data|
|
40
|
+
exit_code = data.read_long
|
41
|
+
end
|
42
|
+
|
43
|
+
chan.on_data do |ch, data|
|
44
|
+
puts data if echo
|
45
|
+
output << data
|
46
|
+
end
|
47
|
+
|
48
|
+
chan.on_extended_data do |ch, type, data|
|
49
|
+
next unless type == 1 # only handle stderr
|
50
|
+
puts data if echo
|
51
|
+
output << data
|
52
|
+
error_output << data
|
53
|
+
end
|
54
|
+
|
55
|
+
chan.exec("#{script} < /dev/null") do |ch, s|
|
56
|
+
raise Exceptions::SshInstallationError, "Error executing script \"#{script[:name]}\"" unless s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
channel.wait
|
61
|
+
|
62
|
+
output
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Webbynode
|
2
|
+
module SshKeys
|
3
|
+
# adds a given key string to a remote server,
|
4
|
+
# creating the key folder structure if needed
|
5
|
+
def add_key_to_remote(key)
|
6
|
+
run_remote_command "mkdir ~/.ssh 2>/dev/null; chmod 700 ~/.ssh; echo \"#{key}\" >> ~/.ssh/authorized_keys; chmod 644 ~/.ssh/authorized_keys"
|
7
|
+
end
|
8
|
+
|
9
|
+
# lists all authorized keys on the remote server
|
10
|
+
def remote_authorized_keys
|
11
|
+
run_remote_command "cat \$HOME/.ssh/authorized_keys"
|
12
|
+
end
|
13
|
+
|
14
|
+
# checks if a remote server has a given key
|
15
|
+
def remote_has_key?(key)
|
16
|
+
if keys = remote_authorized_keys
|
17
|
+
keys.index(key)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# name of the local publish ssh key file
|
22
|
+
def local_key_file
|
23
|
+
"#{ENV['HOME']}/.ssh/id_rsa.pub"
|
24
|
+
end
|
25
|
+
|
26
|
+
# creates the local key with an optional passphrase
|
27
|
+
def create_local_key(passphrase="")
|
28
|
+
run "ssh-keygen -t rsa -N \"#{passphrase}\" -f #{local_key_file}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def local_key(create_when_missing=true)
|
32
|
+
create_local_key(named_options["passphrase"]) if create_when_missing and !File.exists?(local_key_file)
|
33
|
+
@local_key ||= File.read(local_key_file)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/webbynode.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'domainatrix'
|
6
|
+
require 'net/ssh'
|
7
|
+
require 'highline/import'
|
8
|
+
require 'pp'
|
9
|
+
|
10
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'io')
|
11
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'git')
|
12
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'ssh')
|
13
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'server')
|
14
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'push_and')
|
15
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'command')
|
16
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'option')
|
17
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'parameter')
|
18
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'api_client')
|
19
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'remote_executor')
|
20
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'notify')
|
21
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'properties')
|
22
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'init')
|
23
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'push')
|
24
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'config')
|
25
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'add_key')
|
26
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'add_backup')
|
27
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'change_dns')
|
28
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'delete')
|
29
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'remote')
|
30
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'tasks')
|
31
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'start')
|
32
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'stop')
|
33
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'restart')
|
34
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'alias')
|
35
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'help')
|
36
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'webbies')
|
37
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'commands', 'version')
|
38
|
+
|
39
|
+
require File.join(File.dirname(__FILE__), 'webbynode', 'application')
|
40
|
+
|
41
|
+
module Webbynode
|
42
|
+
VERSION = '0.1.2'
|
43
|
+
end
|
44
|
+
|
45
|
+
class Array
|
46
|
+
def to_phrase(last_join="and")
|
47
|
+
return "" if empty?
|
48
|
+
|
49
|
+
array = self.clone
|
50
|
+
last = array.pop
|
51
|
+
|
52
|
+
return last if array.empty?
|
53
|
+
|
54
|
+
"#{array.join(", ")} #{last_join} #{last}"
|
55
|
+
end
|
56
|
+
end
|
File without changes
|
@@ -0,0 +1,30 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Server: nginx/0.6.35
|
3
|
+
Date: Sat, 30 Jan 2010 20:23:40 GMT
|
4
|
+
Content-Type: text/yaml; charset=utf-8
|
5
|
+
Connection: keep-alive
|
6
|
+
Set-Cookie: _webbymanager_session=BAh7BjoPc2Vzc2lvbl9pZCIlODc4ZGUwY2I2YzI2N2E3NjJiMTU4MzUyODZhNjBmM2Q%3D--6fd4a124a19e997aec94772e04070acdde120887; path=/; HttpOnly
|
7
|
+
Status: 200
|
8
|
+
ETag: "ba0c974d33641165d7dcd0b6c0c0363e"
|
9
|
+
X-Runtime: 97
|
10
|
+
Cache-Control: private, max-age=0, must-revalidate
|
11
|
+
Content-Length: 300
|
12
|
+
|
13
|
+
---
|
14
|
+
zones:
|
15
|
+
- :ttl: 86400
|
16
|
+
:domain: rubyista.info.
|
17
|
+
:status: Active
|
18
|
+
:id: 14
|
19
|
+
- :ttl: 86400
|
20
|
+
:domain: controledefrota.com.
|
21
|
+
:status: Active
|
22
|
+
:id: 137
|
23
|
+
- :ttl: 86400
|
24
|
+
:domain: androidreference.com.
|
25
|
+
:status: Active
|
26
|
+
:id: 307
|
27
|
+
- :ttl: 86400
|
28
|
+
:domain: webbyapp.com.
|
29
|
+
:status: Active
|
30
|
+
:id: 979
|
@@ -0,0 +1,20 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Server: nginx/0.6.35
|
3
|
+
Date: Sat, 30 Jan 2010 21:08:17 GMT
|
4
|
+
Content-Type: text/yaml; charset=utf-8
|
5
|
+
Connection: keep-alive
|
6
|
+
Set-Cookie: _webbymanager_session=BAh7BjoPc2Vzc2lvbl9pZCIlYWY1NmJlODIzMjVhMjQ3OThhMTJlODg2YWUzN2NlNDY%3D--68c4db1775911eaf87ad612ab27ef05ae5366943; path=/; HttpOnly
|
7
|
+
Status: 200
|
8
|
+
ETag: "7c4ab3a7f92166784ae648ae2df75520"
|
9
|
+
X-Runtime: 78
|
10
|
+
Cache-Control: private, max-age=0, must-revalidate
|
11
|
+
Content-Length: 110
|
12
|
+
|
13
|
+
---
|
14
|
+
record:
|
15
|
+
:ttl: 86400
|
16
|
+
:type: A
|
17
|
+
:aux: 0
|
18
|
+
:name: xyz
|
19
|
+
:data: 200.100.200.100
|
20
|
+
:id: 7360
|
@@ -0,0 +1,15 @@
|
|
1
|
+
HTTP/1.1 100 Continue
|
2
|
+
|
3
|
+
HTTP/1.1 500 Internal Server Error
|
4
|
+
Server: nginx/0.6.35
|
5
|
+
Date: Sat, 30 Jan 2010 22:40:01 GMT
|
6
|
+
Content-Type: text/yaml; charset=utf-8
|
7
|
+
Connection: keep-alive
|
8
|
+
Set-Cookie: _webbymanager_session=BAh7BjoPc2Vzc2lvbl9pZCIlNTcxODBjZjkyNThiZTUxZWY4MTBkYjQ4NGEwNzNmNTk%3D--5945514460235bcbdd1a97871c0d451be71c2433; path=/; HttpOnly
|
9
|
+
Status: 500
|
10
|
+
X-Runtime: 131
|
11
|
+
Cache-Control: no-cache
|
12
|
+
Content-Length: 41
|
13
|
+
|
14
|
+
---
|
15
|
+
errors: Data has already been taken
|
@@ -0,0 +1,13 @@
|
|
1
|
+
HTTP/1.1 500 Internal Server Error
|
2
|
+
Server: nginx/0.6.35
|
3
|
+
Date: Sat, 30 Jan 2010 21:40:30 GMT
|
4
|
+
Content-Type: text/yaml; charset=utf-8
|
5
|
+
Connection: keep-alive
|
6
|
+
Set-Cookie: _webbymanager_session=BAh7BjoPc2Vzc2lvbl9pZCIlZjA1ZDFkNTVjZDlkNjQ0OGQ3YzRmZTVjODYwZGQyZjc%3D--3f95ecf6d9eae211aee4f2340dce691424f27655; path=/; HttpOnly
|
7
|
+
Status: 500
|
8
|
+
X-Runtime: 5
|
9
|
+
Cache-Control: no-cache
|
10
|
+
Content-Length: 38
|
11
|
+
|
12
|
+
---
|
13
|
+
error: No DNS entry for id 99999
|
@@ -0,0 +1,17 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Server: nginx/0.6.35
|
3
|
+
Date: Sat, 30 Jan 2010 22:59:34 GMT
|
4
|
+
Content-Type: text/yaml; charset=utf-8
|
5
|
+
Connection: keep-alive
|
6
|
+
Set-Cookie: _webbymanager_session=BAh7BjoPc2Vzc2lvbl9pZCIlZjhjNmEwZDE5ZjcyMGFmZTA4MmE2NTM1MDMwNDEyNGU%3D--18e7af8560b1ee3180a9aa1100fdead75243169d; path=/; HttpOnly
|
7
|
+
Status: 200
|
8
|
+
ETag: "08e60ad87837d5d3b26199f5d7b7df92"
|
9
|
+
X-Runtime: 96
|
10
|
+
Cache-Control: private, max-age=0, must-revalidate
|
11
|
+
Content-Length: 70
|
12
|
+
|
13
|
+
---
|
14
|
+
:ttl: 86400
|
15
|
+
:domain: newzone.com.
|
16
|
+
:status: Active
|
17
|
+
:id: 1045
|