jdc 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,332 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'addressable/uri'
4
+
5
+ begin
6
+ require 'caldecott'
7
+ rescue LoadError
8
+ end
9
+
10
+ module JDC::Cli
11
+ module TunnelHelper
12
+ PORT_RANGE = 10
13
+
14
+ HELPER_APP = File.expand_path("../../../caldecott_helper", __FILE__)
15
+
16
+ # bump this AND the version info reported by HELPER_APP/server.rb
17
+ # this is to keep the helper in sync with any updates here
18
+ HELPER_VERSION = '0.0.4'
19
+
20
+ def tunnel_uniquename
21
+ random_service_name(tunnel_appname)
22
+ end
23
+
24
+ def tunnel_appname
25
+ "caldecott"
26
+ end
27
+
28
+ def tunnel_app_info
29
+ return @tun_app_info if @tunnel_app_info
30
+ begin
31
+ @tun_app_info = client.app_info(tunnel_appname)
32
+ rescue => e
33
+ @tun_app_info = nil
34
+ end
35
+ end
36
+
37
+ def tunnel_auth
38
+ tunnel_app_info[:env].each do |e|
39
+ name, val = e.split("=", 2)
40
+ return val if name == "CALDECOTT_AUTH"
41
+ end
42
+ nil
43
+ end
44
+
45
+ def tunnel_url
46
+ return @tunnel_url if @tunnel_url
47
+
48
+ tun_url = tunnel_app_info[:uris][0]
49
+
50
+ ["https", "http"].each do |scheme|
51
+ url = "#{scheme}://#{tun_url}"
52
+ begin
53
+ RestClient.get(url)
54
+
55
+ # https failed
56
+ rescue Errno::ECONNREFUSED
57
+
58
+ # we expect a 404 since this request isn't auth'd
59
+ rescue RestClient::ResourceNotFound
60
+ return @tunnel_url = url
61
+ end
62
+ end
63
+
64
+ err "Cannot determine URL for #{tun_url}"
65
+ end
66
+
67
+ def invalidate_tunnel_app_info
68
+ @tunnel_url = nil
69
+ @tunnel_app_info = nil
70
+ end
71
+
72
+ def tunnel_pushed?
73
+ not tunnel_app_info.nil?
74
+ end
75
+
76
+ def tunnel_healthy?(token)
77
+ return false unless tunnel_app_info[:state] == 'STARTED'
78
+
79
+ begin
80
+ response = RestClient.get(
81
+ "#{tunnel_url}/info",
82
+ "Auth-Token" => token
83
+ )
84
+
85
+ info = JSON.parse(response)
86
+ if info["version"] == HELPER_VERSION
87
+ true
88
+ else
89
+ stop_caldecott
90
+ false
91
+ end
92
+ rescue RestClient::Exception
93
+ stop_caldecott
94
+ false
95
+ end
96
+ end
97
+
98
+ def tunnel_bound?(service)
99
+ tunnel_app_info[:services].include?(service)
100
+ end
101
+
102
+ def tunnel_connection_info(type, service, token)
103
+ display "Getting tunnel connection info: ", false
104
+ response = nil
105
+ 10.times do
106
+ begin
107
+ response = RestClient.get(tunnel_url + "/" + JDC::Client.path("services", service), "Auth-Token" => token)
108
+ break
109
+ rescue RestClient::Exception
110
+ sleep 1
111
+ end
112
+
113
+ display ".", false
114
+ end
115
+
116
+ unless response
117
+ err "Expected remote tunnel to know about #{service}, but it doesn't"
118
+ end
119
+
120
+ display "OK".green
121
+
122
+ info = JSON.parse(response)
123
+ case type
124
+ when "rabbitmq"
125
+ uri = Addressable::URI.parse info["url"]
126
+ info["hostname"] = uri.host
127
+ info["port"] = uri.port
128
+ info["vhost"] = uri.path[1..-1]
129
+ info["user"] = uri.user
130
+ info["password"] = uri.password
131
+ info.delete "url"
132
+
133
+ # we use "db" as the "name" for mongo
134
+ # existing "name" is junk
135
+ when "mongodb"
136
+ info["name"] = info["db"]
137
+ info.delete "db"
138
+
139
+ # our "name" is irrelevant for redis
140
+ when "redis"
141
+ info.delete "name"
142
+ end
143
+
144
+ ['hostname', 'port', 'password'].each do |k|
145
+ err "Could not determine #{k} for #{service}" if info[k].nil?
146
+ end
147
+
148
+ info
149
+ end
150
+
151
+ def display_tunnel_connection_info(info)
152
+ display ''
153
+ display "Service connection info: "
154
+
155
+ to_show = [nil, nil, nil] # reserved for user, pass, db name
156
+ info.keys.each do |k|
157
+ case k
158
+ when "host", "hostname", "port", "node_id"
159
+ # skip
160
+ when "user", "username"
161
+ # prefer "username" over "user"
162
+ to_show[0] = k unless to_show[0] == "username"
163
+ when "password"
164
+ to_show[1] = k
165
+ when "name"
166
+ to_show[2] = k
167
+ else
168
+ to_show << k
169
+ end
170
+ end
171
+ to_show.compact!
172
+
173
+ align_len = to_show.collect(&:size).max + 1
174
+
175
+ to_show.each do |k|
176
+ # TODO: modify the server services rest call to have explicit knowledge
177
+ # about the items to return. It should return all of them if
178
+ # the service is unknown so that we don't have to do this weird
179
+ # filtering.
180
+ display " #{k.ljust align_len}: ", false
181
+ display "#{info[k]}".yellow
182
+ end
183
+ display ''
184
+ end
185
+
186
+ def start_tunnel(local_port, conn_info, auth)
187
+ @local_tunnel_thread = Thread.new do
188
+ Caldecott::Client.start({
189
+ :local_port => local_port,
190
+ :tun_url => tunnel_url,
191
+ :dst_host => conn_info['hostname'],
192
+ :dst_port => conn_info['port'],
193
+ :log_file => STDOUT,
194
+ :log_level => ENV["JDC_TUNNEL_DEBUG"] || "ERROR",
195
+ :auth_token => auth,
196
+ :quiet => true
197
+ })
198
+ end
199
+
200
+ at_exit { @local_tunnel_thread.kill }
201
+ end
202
+
203
+
204
+
205
+ def pick_tunnel_port(port)
206
+ original = port
207
+
208
+ PORT_RANGE.times do |n|
209
+ begin
210
+ TCPSocket.open('localhost', port)
211
+ port += 1
212
+ rescue
213
+ return port
214
+ end
215
+ end
216
+
217
+ grab_ephemeral_port
218
+ end
219
+
220
+ def grab_ephemeral_port
221
+ socket = TCPServer.new('0.0.0.0', 0)
222
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
223
+ Socket.do_not_reverse_lookup = true
224
+ port = socket.addr[1]
225
+ socket.close
226
+ return port
227
+ end
228
+
229
+ def wait_for_tunnel_start(port)
230
+ 10.times do |n|
231
+ begin
232
+ client = TCPSocket.open('localhost', port)
233
+ display '' if n > 0
234
+ client.close
235
+ return true
236
+ rescue => e
237
+ display "Waiting for local tunnel to become available", false if n == 0
238
+ display '.', false
239
+ sleep 1
240
+ end
241
+ end
242
+ err "Could not connect to local tunnel."
243
+ end
244
+
245
+ def wait_for_tunnel_end
246
+ display "Open another shell to run command-line clients or"
247
+ display "use a UI tool to connect using the displayed information."
248
+ display "Press Ctrl-C to exit..."
249
+ @local_tunnel_thread.join
250
+ end
251
+
252
+ def resolve_symbols(str, info, local_port)
253
+ str.gsub(/\$\{\s*([^\}]+)\s*\}/) do
254
+ case $1
255
+ when "host"
256
+ # TODO: determine proper host
257
+ "localhost"
258
+ when "port"
259
+ local_port
260
+ when "user", "username"
261
+ info["username"]
262
+ else
263
+ info[$1] || ask($1)
264
+ end
265
+ end
266
+ end
267
+
268
+ def start_local_prog(clients, command, info, port)
269
+ client = clients[File.basename(command)]
270
+
271
+ cmdline = "#{command} "
272
+
273
+ case client
274
+ when Hash
275
+ cmdline << resolve_symbols(client["command"], info, port)
276
+ client["environment"].each do |e|
277
+ if e =~ /([^=]+)=(["']?)([^"']*)\2/
278
+ ENV[$1] = resolve_symbols($3, info, port)
279
+ else
280
+ err "Invalid environment variable: #{e}"
281
+ end
282
+ end
283
+ when String
284
+ cmdline << resolve_symbols(client, info, port)
285
+ else
286
+ err "Unknown client info: #{client.inspect}."
287
+ end
288
+
289
+ display "Launching '#{cmdline}'"
290
+ display ''
291
+
292
+ system(cmdline)
293
+ end
294
+
295
+ def push_caldecott(token)
296
+ client.create_app(
297
+ tunnel_appname,
298
+ { :name => tunnel_appname,
299
+ :staging => {:framework => "sinatra"},
300
+ :uris => ["#{tunnel_uniquename}.#{target_base}"],
301
+ :instances => 1,
302
+ :resources => {:memory => 64},
303
+ :env => ["CALDECOTT_AUTH=#{token}"]
304
+ }
305
+ )
306
+
307
+ apps_cmd.send(:upload_app_bits, tunnel_appname, HELPER_APP)
308
+
309
+ invalidate_tunnel_app_info
310
+ end
311
+
312
+ def stop_caldecott
313
+ apps_cmd.stop(tunnel_appname)
314
+
315
+ invalidate_tunnel_app_info
316
+ end
317
+
318
+ def start_caldecott
319
+ apps_cmd.start(tunnel_appname)
320
+
321
+ invalidate_tunnel_app_info
322
+ end
323
+
324
+ private
325
+
326
+ def apps_cmd
327
+ a = Command::Apps.new(@options)
328
+ a.client client
329
+ a
330
+ end
331
+ end
332
+ end
data/lib/cli/usage.rb ADDED
@@ -0,0 +1,86 @@
1
+ class JDC::Cli::Runner
2
+
3
+ def basic_usage
4
+ "Usage: jdc [options] command [<args>] [command_options]\n" +
5
+ "Try 'jdc help [command]' or 'jdc help options' for more information."
6
+ end
7
+
8
+ def display_usage
9
+ if @usage
10
+ say @usage_error if @usage_error
11
+ say "Usage: #{@usage}"
12
+ return
13
+ elsif @verb_usage
14
+ say @verb_usage
15
+ return
16
+ end
17
+ say command_usage
18
+ end
19
+
20
+ def command_usage
21
+ <<-USAGE
22
+
23
+ #{basic_usage}
24
+
25
+ Currently available jdc commands are:
26
+
27
+ Getting Started
28
+ target [url] Reports current target or sets a new target
29
+ info System and account information
30
+
31
+ Applications
32
+ user Display current user account information
33
+ apps List deployed applications
34
+
35
+ Application Creation
36
+ push [appname] Create, push, map, and start a new application
37
+ push [appname] --path Push application from specified path
38
+ push [appname] --url Set the url for the application
39
+ push [appname] --instances <N> Set the expected number <N> of instances
40
+ push [appname] --mem M Set the memory reservation for the application
41
+ push [appname] --runtime RUNTIME Set the runtime to use for the application
42
+ push [appname] --debug [MODE] Push application and start in a debug mode
43
+ push [appname] --no-start Do not auto-start the application
44
+
45
+ Application Operations
46
+ start <appname> [--debug [MODE]] Start the application
47
+ stop <appname> Stop the application
48
+ restart <appname> [--debug [MODE]] Restart the application
49
+ delete <appname> Delete the application
50
+
51
+ Application Updates
52
+ update <appname> [--path,--debug [MODE]] Update the application bits
53
+ mem <appname> [memsize] Update the memory reservation for an application
54
+ map <appname> <url> Register the application to the url
55
+ unmap <appname> <url> Unregister the application from the url
56
+ instances <appname> <num|delta> Scale the application instances up or down
57
+
58
+ Application Information
59
+ crashes <appname> List recent application crashes
60
+ crashlogs <appname> Display log information for crashed applications
61
+ logs <appname> [--all] Display log information for the application
62
+ files <appname> [path] [--all] Display directory listing or file download for [path]
63
+ stats <appname> Display resource usage for the application
64
+ instances <appname> List application instances
65
+
66
+ Application Environment
67
+ env <appname> List application environment variables
68
+ env-add <appname> <variable[=]value> Add an environment variable to an application
69
+ env-del <appname> <variable> Delete an environment variable to an application
70
+
71
+ System
72
+ runtimes Display the supported runtimes of the target system
73
+ frameworks Display the recognized frameworks of the target system
74
+
75
+ Misc
76
+ aliases List aliases
77
+ alias <alias[=]command> Create an alias for a command
78
+ unalias <alias> Remove an alias
79
+
80
+ Help
81
+ help [command] Get general help or help on a specific command
82
+ help options Get help on available options
83
+ USAGE
84
+
85
+ end
86
+ end
@@ -0,0 +1,7 @@
1
+ module JDC
2
+ module Cli
3
+ # This version number is used as the RubyGem release version.
4
+ # The internal JDC version number is JDC::VERSION.
5
+ VERSION = '0.1.1'
6
+ end
7
+ end
@@ -0,0 +1,77 @@
1
+
2
+ require 'zip/zipfilesystem'
3
+
4
+ module JDC::Cli
5
+
6
+ class ZipUtil
7
+
8
+ PACK_EXCLUSION_GLOBS = ['..', '.', '*~', '#*#', '*.log']
9
+
10
+ class << self
11
+
12
+ def to_dev_null
13
+ if WINDOWS
14
+ 'nul'
15
+ else
16
+ '/dev/null'
17
+ end
18
+ end
19
+
20
+ def entry_lines(file)
21
+ contents = nil
22
+ unless JDC::Cli::Config.nozip
23
+ contents = `unzip -l #{file} 2> #{to_dev_null}`
24
+ contents = nil if $? != 0
25
+ end
26
+ # Do Ruby version if told to or native version failed
27
+ unless contents
28
+ entries = []
29
+ Zip::ZipFile.foreach(file) { |zentry| entries << zentry }
30
+ contents = entries.join("\n")
31
+ end
32
+ contents
33
+ end
34
+
35
+ def unpack(file, dest)
36
+ unless JDC::Cli::Config.nozip
37
+ FileUtils.mkdir(dest)
38
+ `unzip -q #{file} -d #{dest} 2> #{to_dev_null}`
39
+ return unless $? != 0
40
+ end
41
+ # Do Ruby version if told to or native version failed
42
+ Zip::ZipFile.foreach(file) do |zentry|
43
+ epath = "#{dest}/#{zentry}"
44
+ dirname = File.dirname(epath)
45
+ FileUtils.mkdir_p(dirname) unless File.exists?(dirname)
46
+ zentry.extract(epath) unless File.exists?(epath)
47
+ end
48
+ end
49
+
50
+ def get_files_to_pack(dir)
51
+ Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH).select do |f|
52
+ process = true
53
+ PACK_EXCLUSION_GLOBS.each { |e| process = false if File.fnmatch(e, File.basename(f)) }
54
+ process && File.exists?(f)
55
+ end
56
+ end
57
+
58
+ def pack(dir, zipfile)
59
+ unless JDC::Cli::Config.nozip
60
+ excludes = PACK_EXCLUSION_GLOBS.map { |e| "\\#{e}" }
61
+ excludes = excludes.join(' ')
62
+ Dir.chdir(dir) do
63
+ `zip -y -q -r #{zipfile} . -x #{excludes} 2> #{to_dev_null}`
64
+ return unless $? != 0
65
+ end
66
+ end
67
+ # Do Ruby version if told to or native version failed
68
+ Zip::ZipFile::open(zipfile, true) do |zf|
69
+ get_files_to_pack(dir).each do |f|
70
+ zf.add(f.sub("#{dir}/",''), f)
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+ end
77
+ end
data/lib/cli.rb ADDED
@@ -0,0 +1,53 @@
1
+ require "rbconfig"
2
+
3
+ ROOT = File.expand_path(File.dirname(__FILE__))
4
+ WINDOWS = !!(RbConfig::CONFIG['host_os'] =~ /mingw|mswin32|cygwin/)
5
+
6
+ module JDC
7
+ autoload :Client, "#{ROOT}/jdc/client"
8
+ autoload :Micro, "#{ROOT}/jdc/micro"
9
+ autoload :Timer, "#{ROOT}/jdc/timer"
10
+ autoload :Signer, "#{ROOT}/jdc/signer"
11
+
12
+ module Micro
13
+ module Switcher
14
+ autoload :Base, "#{ROOT}/jdc/micro/switcher/base"
15
+ autoload :Darwin, "#{ROOT}/jdc/micro/switcher/darwin"
16
+ autoload :Dummy, "#{ROOT}/jdc/micro/switcher/dummy"
17
+ autoload :Linux, "#{ROOT}/jdc/micro/switcher/linux"
18
+ autoload :Windows, "#{ROOT}/jdc/micro/switcher/windows"
19
+ end
20
+ autoload :VMrun, "#{ROOT}/jdc/micro/vmrun"
21
+ end
22
+
23
+ module Signature
24
+ autoload :Version, "#{ROOT}/jdc/signature/version"
25
+ end
26
+
27
+ module Cli
28
+ autoload :Config, "#{ROOT}/cli/config"
29
+ autoload :Framework, "#{ROOT}/cli/frameworks"
30
+ autoload :Runner, "#{ROOT}/cli/runner"
31
+ autoload :ZipUtil, "#{ROOT}/cli/zip_util"
32
+ autoload :ServicesHelper, "#{ROOT}/cli/services_helper"
33
+ autoload :TunnelHelper, "#{ROOT}/cli/tunnel_helper"
34
+ autoload :ManifestHelper, "#{ROOT}/cli/manifest_helper"
35
+ autoload :ConsoleHelper, "#{ROOT}/cli/console_helper"
36
+
37
+ module Command
38
+ autoload :Base, "#{ROOT}/cli/commands/base"
39
+ autoload :Admin, "#{ROOT}/cli/commands/admin"
40
+ autoload :Apps, "#{ROOT}/cli/commands/apps"
41
+ autoload :Micro, "#{ROOT}/cli/commands/micro"
42
+ autoload :Misc, "#{ROOT}/cli/commands/misc"
43
+ autoload :Services, "#{ROOT}/cli/commands/services"
44
+ autoload :User, "#{ROOT}/cli/commands/user"
45
+ autoload :Manifest, "#{ROOT}/cli/commands/manifest"
46
+ end
47
+
48
+ end
49
+ end
50
+
51
+ require "#{ROOT}/cli/version"
52
+ require "#{ROOT}/cli/core_ext"
53
+ require "#{ROOT}/cli/errors"