paasio 0.3.16.beta.2

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,324 @@
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 VMC::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
+
43
+ nil
44
+ end
45
+
46
+ def tunnel_url
47
+ return @tunnel_url if @tunnel_url
48
+
49
+ tun_url = tunnel_app_info[:uris][0]
50
+
51
+ ["https", "http"].each do |scheme|
52
+ url = "#{scheme}://#{tun_url}"
53
+ begin
54
+ RestClient.get(url)
55
+
56
+ # https failed
57
+ rescue Errno::ECONNREFUSED
58
+
59
+ # we expect a 404 since this request isn't auth'd
60
+ rescue RestClient::ResourceNotFound
61
+ return @tunnel_url = url
62
+ end
63
+ end
64
+
65
+ err "Cannot determine URL for #{tun_url}"
66
+ end
67
+
68
+ def invalidate_tunnel_app_info
69
+ @tunnel_url = nil
70
+ @tunnel_app_info = nil
71
+ end
72
+
73
+ def tunnel_pushed?
74
+ not tunnel_app_info.nil?
75
+ end
76
+
77
+ def tunnel_healthy?(token)
78
+ return false unless tunnel_app_info[:state] == 'STARTED'
79
+
80
+ begin
81
+ response = RestClient.get(
82
+ "#{tunnel_url}/info",
83
+ "Auth-Token" => token
84
+ )
85
+
86
+ info = JSON.parse(response)
87
+ if info["version"] == HELPER_VERSION
88
+ true
89
+ else
90
+ stop_caldecott
91
+ false
92
+ end
93
+ rescue RestClient::Exception
94
+ stop_caldecott
95
+ false
96
+ end
97
+ end
98
+
99
+ def tunnel_bound?(service)
100
+ tunnel_app_info[:services].include?(service)
101
+ end
102
+
103
+ def tunnel_connection_info(type, service, token)
104
+ display "Getting tunnel connection info: ", false
105
+ response = nil
106
+ 10.times do
107
+ begin
108
+ response = RestClient.get(tunnel_url + "/" + VMC::Client.path("services", service), "Auth-Token" => token)
109
+ break
110
+ rescue RestClient::Exception
111
+ sleep 1
112
+ end
113
+
114
+ display ".", false
115
+ end
116
+
117
+ unless response
118
+ err "Expected remote tunnel to know about #{service}, but it doesn't"
119
+ end
120
+
121
+ display "OK".green
122
+
123
+ info = JSON.parse(response)
124
+ case type
125
+ when "rabbitmq"
126
+ uri = Addressable::URI.parse info["url"]
127
+ info["hostname"] = uri.host
128
+ info["port"] = uri.port
129
+ info["vhost"] = uri.path[1..-1]
130
+ info["user"] = uri.user
131
+ info["password"] = uri.password
132
+ info.delete "url"
133
+
134
+ # we use "db" as the "name" for mongo
135
+ # existing "name" is junk
136
+ when "mongodb"
137
+ info["name"] = info["db"]
138
+ info.delete "db"
139
+
140
+ # our "name" is irrelevant for redis
141
+ when "redis"
142
+ info.delete "name"
143
+ end
144
+
145
+ ['hostname', 'port', 'password'].each do |k|
146
+ err "Could not determine #{k} for #{service}" if info[k].nil?
147
+ end
148
+
149
+ info
150
+ end
151
+
152
+ def display_tunnel_connection_info(info)
153
+ display ''
154
+ display "Service connection info: "
155
+
156
+ to_show = [nil, nil, nil] # reserved for user, pass, db name
157
+ info.keys.each do |k|
158
+ case k
159
+ when "host", "hostname", "port", "node_id"
160
+ # skip
161
+ when "user", "username"
162
+ # prefer "username" over "user"
163
+ to_show[0] = k unless to_show[0] == "username"
164
+ when "password"
165
+ to_show[1] = k
166
+ when "name"
167
+ to_show[2] = k
168
+ else
169
+ to_show << k
170
+ end
171
+ end
172
+ to_show.compact!
173
+
174
+ align_len = to_show.collect(&:size).max + 1
175
+
176
+ to_show.each do |k|
177
+ # TODO: modify the server services rest call to have explicit knowledge
178
+ # about the items to return. It should return all of them if
179
+ # the service is unknown so that we don't have to do this weird
180
+ # filtering.
181
+ display " #{k.ljust align_len}: ", false
182
+ display "#{info[k]}".yellow
183
+ end
184
+ display ''
185
+ end
186
+
187
+ def start_tunnel(service, local_port, conn_info, auth)
188
+ display "Starting tunnel to #{service.bold} on port #{local_port.to_s.bold}."
189
+
190
+ @local_tunnel_thread = Thread.new do
191
+ Caldecott::Client.start({
192
+ :local_port => local_port,
193
+ :tun_url => tunnel_url,
194
+ :dst_host => conn_info['hostname'],
195
+ :dst_port => conn_info['port'],
196
+ :log_file => STDOUT,
197
+ :log_level => ENV["VMC_TUNNEL_DEBUG"] || "ERROR",
198
+ :auth_token => auth,
199
+ :quiet => true
200
+ })
201
+ end
202
+
203
+ at_exit { @local_tunnel_thread.kill }
204
+ end
205
+
206
+ def pick_tunnel_port(port)
207
+ original = port
208
+
209
+ PORT_RANGE.times do |n|
210
+ begin
211
+ TCPSocket.open('localhost', port)
212
+ port += 1
213
+ rescue
214
+ return port
215
+ end
216
+ end
217
+
218
+ grab_ephemeral_port
219
+ end
220
+
221
+ def grab_ephemeral_port
222
+ socket = TCPServer.new('0.0.0.0', 0)
223
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
224
+ Socket.do_not_reverse_lookup = true
225
+ port = socket.addr[1]
226
+ socket.close
227
+ return port
228
+ end
229
+
230
+ def wait_for_tunnel_start(port)
231
+ 10.times do |n|
232
+ begin
233
+ TCPSocket.open('localhost', port)
234
+ display '' if n > 0
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}.#{VMC::Cli::Config.suggest_url}"],
301
+ :instances => 1,
302
+ :resources => {:memory => 64},
303
+ :env => ["CALDECOTT_AUTH=#{token}"]
304
+ }
305
+ )
306
+
307
+ Command::Apps.new(@options).send(:upload_app_bits, tunnel_appname, HELPER_APP)
308
+
309
+ invalidate_tunnel_app_info
310
+ end
311
+
312
+ def stop_caldecott
313
+ Command::Apps.new(@options).stop(tunnel_appname)
314
+
315
+ invalidate_tunnel_app_info
316
+ end
317
+
318
+ def start_caldecott
319
+ Command::Apps.new(@options).start(tunnel_appname)
320
+
321
+ invalidate_tunnel_app_info
322
+ end
323
+ end
324
+ end
data/lib/cli/usage.rb ADDED
@@ -0,0 +1,104 @@
1
+ class VMC::Cli::Runner
2
+
3
+ def basic_usage
4
+ "Usage: paasio [options] command [<args>] [command_options]\n" +
5
+ "Try 'paasio help [command]' or 'paasio 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 paasio commands are:
26
+
27
+ Getting Started
28
+ login [email] [--email, --passwd] Login
29
+ info System and account information
30
+
31
+ Applications
32
+ apps List deployed applications
33
+
34
+ Application Creation
35
+ create [appname] Create a new application
36
+ create [appname] --url Set the url for the application
37
+ create [appname] --instances <N> Set the expected number <N> of instances
38
+ create [appname] --mem M Set the memory reservation for the application
39
+ create [appname] --runtime RUNTIME Set the runtime to use for the application
40
+ create [appname] --debug [MODE] Push application and start in a debug mode
41
+
42
+ Application Operations
43
+ start <appname> [--debug [MODE]] Start the application
44
+ stop <appname> Stop the application
45
+ restart <appname> [--debug [MODE]] Restart the application
46
+ delete <appname> Delete the application
47
+ rename <appname> <newname> Rename the application
48
+
49
+ Application Updates
50
+ upload <appname> [--path,--debug [MODE]] Upload the application bits
51
+ mem <appname> [memsize] Update the memory reservation for an application
52
+ map <appname> <url> Register the application to the url
53
+ unmap <appname> <url> Unregister the application from the url
54
+ instances <appname> <num|delta> Scale the application instances up or down
55
+
56
+ Application Information
57
+ crashes <appname> List recent application crashes
58
+ crashlogs <appname> Display log information for crashed applications
59
+ logs <appname> [--all] Display log information for the application
60
+ files <appname> [path] [--all] Display directory listing or file download for [path]
61
+ stats <appname> Display resource usage for the application
62
+ instances <appname> List application instances
63
+
64
+ Application Environment
65
+ env <appname> List application environment variables
66
+ env-add <appname> <variable[=]value> Add an environment variable to an application
67
+ env-del <appname> <variable> Delete an environment variable to an application
68
+
69
+ Services
70
+ services Lists of services available and provisioned
71
+ create-service <service> [--name,--bind] Create a provisioned service
72
+ create-service <service> <name> Create a provisioned service and assign it <name>
73
+ create-service <service> <name> <app> Create a provisioned service and assign it <name>, and bind to <app>
74
+ delete-service [servicename] Delete a provisioned service
75
+ bind-service <servicename> <appname> Bind a service to an application
76
+ unbind-service <servicename> <appname> Unbind service from the application
77
+ clone-services <src-app> <dest-app> Clone service bindings from <src-app> application to <dest-app>
78
+ tunnel <servicename> [--port] Create a local tunnel to a service
79
+ tunnel <servicename> <clientcmd> Create a local tunnel to a service and start a local client
80
+
81
+ Administration
82
+ user Display user account information
83
+ passwd Change the password for the current user
84
+ logout Logs current user out of the target system
85
+ add-user [--email, --passwd] Register a new user (requires admin privileges)
86
+ delete-user <user> Delete a user and all apps and services (requires admin privileges)
87
+
88
+ System
89
+ runtimes Display the supported runtimes of the target system
90
+ frameworks Display the recognized frameworks of the target system
91
+
92
+ Misc
93
+ aliases List aliases
94
+ alias <alias[=]command> Create an alias for a command
95
+ unalias <alias> Remove an alias
96
+ targets List known targets and associated authorization tokens
97
+
98
+ Help
99
+ help [command] Get general help or help on a specific command
100
+ help options Get help on available options
101
+ USAGE
102
+
103
+ end
104
+ end
@@ -0,0 +1,7 @@
1
+ module VMC
2
+ module Cli
3
+ # This version number is used as the RubyGem release version.
4
+ # The internal VMC version number is VMC::VERSION.
5
+ VERSION = '0.3.16.beta.2'
6
+ end
7
+ end
@@ -0,0 +1,77 @@
1
+
2
+ require 'zip/zipfilesystem'
3
+
4
+ module VMC::Cli
5
+
6
+ class ZipUtil
7
+
8
+ PACK_EXCLUSION_GLOBS = ['..', '.', '*~', '#*#', '*.log']
9
+
10
+ class << self
11
+
12
+ def to_dev_null
13
+ if !!RUBY_PLATFORM['mingw'] || !!RUBY_PLATFORM['mswin32'] || !!RUBY_PLATFORM['cygwin']
14
+ 'nul'
15
+ else
16
+ '/dev/null'
17
+ end
18
+ end
19
+
20
+ def entry_lines(file)
21
+ contents = nil
22
+ unless VMC::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 VMC::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 VMC::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,32 @@
1
+ ROOT = File.expand_path(File.dirname(__FILE__))
2
+ WINDOWS = !!(RUBY_PLATFORM =~ /mingw|mswin32|cygwin/)
3
+
4
+
5
+ module VMC
6
+ autoload :Client, "#{ROOT}/vmc/client"
7
+
8
+ module Cli
9
+ autoload :Config, "#{ROOT}/cli/config"
10
+ autoload :Framework, "#{ROOT}/cli/frameworks"
11
+ autoload :Runner, "#{ROOT}/cli/runner"
12
+ autoload :ZipUtil, "#{ROOT}/cli/zip_util"
13
+ autoload :ServicesHelper, "#{ROOT}/cli/services_helper"
14
+ autoload :TunnelHelper, "#{ROOT}/cli/tunnel_helper"
15
+ autoload :ManifestHelper, "#{ROOT}/cli/manifest_helper"
16
+
17
+ module Command
18
+ autoload :Base, "#{ROOT}/cli/commands/base"
19
+ autoload :Admin, "#{ROOT}/cli/commands/admin"
20
+ autoload :Apps, "#{ROOT}/cli/commands/apps"
21
+ autoload :Misc, "#{ROOT}/cli/commands/misc"
22
+ autoload :Services, "#{ROOT}/cli/commands/services"
23
+ autoload :User, "#{ROOT}/cli/commands/user"
24
+ autoload :Manifest, "#{ROOT}/cli/commands/manifest"
25
+ end
26
+
27
+ end
28
+ end
29
+
30
+ require "#{ROOT}/cli/version"
31
+ require "#{ROOT}/cli/core_ext"
32
+ require "#{ROOT}/cli/errors"