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.

Files changed (98) hide show
  1. data/Manifest +88 -4
  2. data/PostInstall.txt +42 -9
  3. data/README.rdoc +6 -0
  4. data/Rakefile +16 -19
  5. data/assets/webbynode.png +0 -0
  6. data/bin/webbynode +3 -3
  7. data/bin/wn +7 -0
  8. data/cucumber.yml +1 -0
  9. data/devver.rake +174 -0
  10. data/features/bootstrap.feature +17 -0
  11. data/features/step_definitions/command_steps.rb +14 -0
  12. data/features/support/env.rb +22 -0
  13. data/features/support/hooks.rb +8 -0
  14. data/{test/test_webbynode.rb → features/support/io_features.rb} +0 -0
  15. data/features/support/mocha.rb +15 -0
  16. data/lib/templates/api_token +14 -0
  17. data/lib/templates/backup +15 -0
  18. data/lib/templates/gitignore +4 -0
  19. data/lib/templates/help +53 -0
  20. data/lib/webbynode/api_client.rb +121 -0
  21. data/lib/webbynode/application.rb +21 -0
  22. data/lib/webbynode/command.rb +332 -0
  23. data/lib/webbynode/commands/add_backup.rb +33 -0
  24. data/lib/webbynode/commands/add_key.rb +24 -0
  25. data/lib/webbynode/commands/alias.rb +153 -0
  26. data/lib/webbynode/commands/change_dns.rb +29 -0
  27. data/lib/webbynode/commands/config.rb +15 -0
  28. data/lib/webbynode/commands/delete.rb +25 -0
  29. data/lib/webbynode/commands/help.rb +25 -0
  30. data/lib/webbynode/commands/init.rb +77 -0
  31. data/lib/webbynode/commands/push.rb +70 -0
  32. data/lib/webbynode/commands/remote.rb +28 -0
  33. data/lib/webbynode/commands/restart.rb +25 -0
  34. data/lib/webbynode/commands/start.rb +23 -0
  35. data/lib/webbynode/commands/stop.rb +23 -0
  36. data/lib/webbynode/commands/tasks.rb +149 -0
  37. data/lib/webbynode/commands/version.rb +8 -0
  38. data/lib/webbynode/commands/webbies.rb +30 -0
  39. data/lib/webbynode/git.rb +112 -0
  40. data/lib/webbynode/io.rb +175 -0
  41. data/lib/webbynode/notify.rb +19 -0
  42. data/lib/webbynode/option.rb +84 -0
  43. data/lib/webbynode/parameter.rb +27 -0
  44. data/lib/webbynode/properties.rb +43 -0
  45. data/lib/webbynode/push_and.rb +16 -0
  46. data/lib/webbynode/remote_executor.rb +21 -0
  47. data/lib/webbynode/server.rb +39 -0
  48. data/lib/webbynode/ssh.rb +65 -0
  49. data/lib/webbynode/ssh_keys.rb +36 -0
  50. data/lib/webbynode.rb +56 -0
  51. data/spec/fixtures/aliases +0 -0
  52. data/spec/fixtures/api/credentials +3 -0
  53. data/spec/fixtures/api/dns +30 -0
  54. data/spec/fixtures/api/dns_a_record +20 -0
  55. data/spec/fixtures/api/dns_a_record_already_exists +15 -0
  56. data/spec/fixtures/api/dns_a_record_error +13 -0
  57. data/spec/fixtures/api/dns_new_zone +17 -0
  58. data/spec/fixtures/api/webbies +26 -0
  59. data/spec/fixtures/api/webbies_unauthorized +12 -0
  60. data/spec/fixtures/api/webby +6 -0
  61. data/spec/fixtures/commands/tasks/after_push +3 -0
  62. data/spec/fixtures/fixture_helpers +2 -0
  63. data/spec/fixtures/git/config/210.11.13.12 +9 -0
  64. data/spec/fixtures/git/config/67.23.79.31 +9 -0
  65. data/spec/fixtures/git/config/67.23.79.32 +9 -0
  66. data/spec/fixtures/git/config/config +9 -0
  67. data/spec/fixtures/git/status/clean +2 -0
  68. data/spec/fixtures/git/status/dirty +9 -0
  69. data/spec/fixtures/pushand +2 -0
  70. data/spec/spec_helper.rb +58 -0
  71. data/spec/webbynode/api_client_spec.rb +227 -0
  72. data/spec/webbynode/application_spec.rb +50 -0
  73. data/spec/webbynode/command_spec.rb +285 -0
  74. data/spec/webbynode/commands/add_backup_spec.rb +66 -0
  75. data/spec/webbynode/commands/add_key_spec.rb +58 -0
  76. data/spec/webbynode/commands/alias_spec.rb +213 -0
  77. data/spec/webbynode/commands/change_dns_spec.rb +51 -0
  78. data/spec/webbynode/commands/config_spec.rb +22 -0
  79. data/spec/webbynode/commands/delete_spec.rb +78 -0
  80. data/spec/webbynode/commands/help_spec.rb +43 -0
  81. data/spec/webbynode/commands/init_spec.rb +326 -0
  82. data/spec/webbynode/commands/push_spec.rb +175 -0
  83. data/spec/webbynode/commands/remote_spec.rb +76 -0
  84. data/spec/webbynode/commands/tasks_spec.rb +288 -0
  85. data/spec/webbynode/commands/version_spec.rb +18 -0
  86. data/spec/webbynode/commands/webbies_spec.rb +27 -0
  87. data/spec/webbynode/git_spec.rb +310 -0
  88. data/spec/webbynode/io_spec.rb +198 -0
  89. data/spec/webbynode/option_spec.rb +48 -0
  90. data/spec/webbynode/parameter_spec.rb +70 -0
  91. data/spec/webbynode/push_and_spec.rb +28 -0
  92. data/spec/webbynode/remote_executor_spec.rb +25 -0
  93. data/spec/webbynode/server_spec.rb +101 -0
  94. data/webbynode.gemspec +25 -16
  95. metadata +182 -14
  96. data/lib/wn.rb +0 -106
  97. data/test/test_helper.rb +0 -9
  98. data/test/test_wn.rb +0 -220
@@ -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,3 @@
1
+ ---
2
+ :token: apitoken
3
+ :email: fcoury@me.com
@@ -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