vmc 0.4.0.beta.9 → 0.4.0.beta.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/bin/vmc +11 -10
  2. data/{LICENSE → vmc-ng/LICENSE} +0 -0
  3. data/{Rakefile → vmc-ng/Rakefile} +0 -0
  4. data/vmc-ng/bin/vmc +14 -0
  5. data/{lib → vmc-ng/lib}/vmc.rb +0 -0
  6. data/{lib → vmc-ng/lib}/vmc/cli.rb +0 -0
  7. data/{lib → vmc-ng/lib}/vmc/cli/app.rb +0 -0
  8. data/{lib → vmc-ng/lib}/vmc/cli/better_help.rb +0 -0
  9. data/{lib → vmc-ng/lib}/vmc/cli/command.rb +0 -0
  10. data/{lib → vmc-ng/lib}/vmc/cli/dots.rb +0 -0
  11. data/{lib → vmc-ng/lib}/vmc/cli/service.rb +0 -0
  12. data/{lib → vmc-ng/lib}/vmc/cli/user.rb +0 -0
  13. data/{lib → vmc-ng/lib}/vmc/constants.rb +0 -0
  14. data/{lib → vmc-ng/lib}/vmc/detect.rb +0 -0
  15. data/{lib → vmc-ng/lib}/vmc/errors.rb +0 -0
  16. data/{lib → vmc-ng/lib}/vmc/plugin.rb +0 -0
  17. data/vmc-ng/lib/vmc/version.rb +3 -0
  18. data/vmc/LICENSE +24 -0
  19. data/vmc/README.md +102 -0
  20. data/vmc/Rakefile +101 -0
  21. data/vmc/bin/vmc +6 -0
  22. data/vmc/caldecott_helper/Gemfile +10 -0
  23. data/vmc/caldecott_helper/Gemfile.lock +48 -0
  24. data/vmc/caldecott_helper/server.rb +43 -0
  25. data/vmc/config/clients.yml +17 -0
  26. data/vmc/config/micro/offline.conf +2 -0
  27. data/vmc/config/micro/paths.yml +22 -0
  28. data/vmc/config/micro/refresh_ip.rb +20 -0
  29. data/vmc/lib/cli.rb +47 -0
  30. data/vmc/lib/cli/commands/admin.rb +80 -0
  31. data/vmc/lib/cli/commands/apps.rb +1126 -0
  32. data/vmc/lib/cli/commands/base.rb +227 -0
  33. data/vmc/lib/cli/commands/manifest.rb +56 -0
  34. data/vmc/lib/cli/commands/micro.rb +115 -0
  35. data/vmc/lib/cli/commands/misc.rb +129 -0
  36. data/vmc/lib/cli/commands/services.rb +180 -0
  37. data/vmc/lib/cli/commands/user.rb +65 -0
  38. data/vmc/lib/cli/config.rb +173 -0
  39. data/vmc/lib/cli/console_helper.rb +160 -0
  40. data/vmc/lib/cli/core_ext.rb +122 -0
  41. data/vmc/lib/cli/errors.rb +19 -0
  42. data/vmc/lib/cli/frameworks.rb +265 -0
  43. data/vmc/lib/cli/manifest_helper.rb +302 -0
  44. data/vmc/lib/cli/runner.rb +531 -0
  45. data/vmc/lib/cli/services_helper.rb +84 -0
  46. data/vmc/lib/cli/tunnel_helper.rb +332 -0
  47. data/vmc/lib/cli/usage.rb +115 -0
  48. data/vmc/lib/cli/version.rb +7 -0
  49. data/vmc/lib/cli/zip_util.rb +77 -0
  50. data/vmc/lib/vmc.rb +3 -0
  51. data/vmc/lib/vmc/client.rb +471 -0
  52. data/vmc/lib/vmc/const.rb +22 -0
  53. data/vmc/lib/vmc/micro.rb +56 -0
  54. data/vmc/lib/vmc/micro/switcher/base.rb +97 -0
  55. data/vmc/lib/vmc/micro/switcher/darwin.rb +19 -0
  56. data/vmc/lib/vmc/micro/switcher/dummy.rb +15 -0
  57. data/vmc/lib/vmc/micro/switcher/linux.rb +16 -0
  58. data/vmc/lib/vmc/micro/switcher/windows.rb +31 -0
  59. data/vmc/lib/vmc/micro/vmrun.rb +158 -0
  60. metadata +267 -35
  61. data/lib/vmc/version.rb +0 -3
@@ -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.18'
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 WINDOWS
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/vmc/lib/vmc.rb ADDED
@@ -0,0 +1,3 @@
1
+ module VMC; end
2
+
3
+ require 'vmc/client'
@@ -0,0 +1,471 @@
1
+ # VMC client
2
+ #
3
+ # Example:
4
+ #
5
+ # require 'vmc'
6
+ # client = VMC::Client.new('api.vcap.me')
7
+ # client.login(:user, :pass)
8
+ # client.create('myapplication', manifest)
9
+ # client.create_service('redis', 'my_redis_service', opts);
10
+ #
11
+
12
+ require 'rubygems'
13
+ require 'json/pure'
14
+ require 'open-uri'
15
+
16
+ require File.expand_path('../const', __FILE__)
17
+
18
+ class VMC::Client
19
+
20
+ def self.version
21
+ VMC::VERSION
22
+ end
23
+
24
+ attr_reader :target, :host, :user, :proxy, :auth_token
25
+ attr_accessor :trace
26
+
27
+ # Error codes
28
+ VMC_HTTP_ERROR_CODES = [ 400, 500 ]
29
+
30
+ # Errors
31
+ class BadTarget < RuntimeError; end
32
+ class AuthError < RuntimeError; end
33
+ class TargetError < RuntimeError; end
34
+ class NotFound < RuntimeError; end
35
+ class BadResponse < RuntimeError; end
36
+ class HTTPException < RuntimeError; end
37
+
38
+ # Initialize new client to the target_uri with optional auth_token
39
+ def initialize(target_url=VMC::DEFAULT_TARGET, auth_token=nil)
40
+ target_url = "http://#{target_url}" unless /^https?/ =~ target_url
41
+ target_url = target_url.gsub(/\/+$/, '')
42
+ @target = target_url
43
+ @auth_token = auth_token
44
+ end
45
+
46
+ ######################################################
47
+ # Target info
48
+ ######################################################
49
+
50
+ # Retrieves information on the target cloud, and optionally the logged in user
51
+ def info
52
+ # TODO: Should merge for new version IMO, general, services, user_account
53
+ json_get(VMC::INFO_PATH)
54
+ end
55
+
56
+ def raw_info
57
+ http_get(VMC::INFO_PATH)
58
+ end
59
+
60
+ # Global listing of services that are available on the target system
61
+ def services_info
62
+ check_login_status
63
+ json_get(path(VMC::GLOBAL_SERVICES_PATH))
64
+ end
65
+
66
+ def runtimes_info
67
+ json_get(path(VMC::GLOBAL_RUNTIMES_PATH))
68
+ end
69
+
70
+ ######################################################
71
+ # Apps
72
+ ######################################################
73
+
74
+ def apps
75
+ check_login_status
76
+ json_get(VMC::APPS_PATH)
77
+ end
78
+
79
+ def create_app(name, manifest={})
80
+ check_login_status
81
+ app = manifest.dup
82
+ app[:name] = name
83
+ app[:instances] ||= 1
84
+ json_post(VMC::APPS_PATH, app)
85
+ end
86
+
87
+ def update_app(name, manifest)
88
+ check_login_status
89
+ json_put(path(VMC::APPS_PATH, name), manifest)
90
+ end
91
+
92
+ def upload_app(name, zipfile, resource_manifest=nil)
93
+ #FIXME, manifest should be allowed to be null, here for compatability with old cc's
94
+ resource_manifest ||= []
95
+ check_login_status
96
+ upload_data = {:_method => 'put'}
97
+ if zipfile
98
+ if zipfile.is_a? File
99
+ file = zipfile
100
+ else
101
+ file = File.new(zipfile, 'rb')
102
+ end
103
+ upload_data[:application] = file
104
+ end
105
+ upload_data[:resources] = resource_manifest.to_json if resource_manifest
106
+ http_post(path(VMC::APPS_PATH, name, "application"), upload_data)
107
+ rescue RestClient::ServerBrokeConnection
108
+ retry
109
+ end
110
+
111
+ def delete_app(name)
112
+ check_login_status
113
+ http_delete(path(VMC::APPS_PATH, name))
114
+ end
115
+
116
+ def app_info(name)
117
+ check_login_status
118
+ json_get(path(VMC::APPS_PATH, name))
119
+ end
120
+
121
+ def app_update_info(name)
122
+ check_login_status
123
+ json_get(path(VMC::APPS_PATH, name, "update"))
124
+ end
125
+
126
+ def app_stats(name)
127
+ check_login_status
128
+ stats_raw = json_get(path(VMC::APPS_PATH, name, "stats"))
129
+ stats = []
130
+ stats_raw.each_pair do |k, entry|
131
+ # Skip entries with no stats
132
+ next unless entry[:stats]
133
+ entry[:instance] = k.to_s.to_i
134
+ entry[:state] = entry[:state].to_sym if entry[:state]
135
+ stats << entry
136
+ end
137
+ stats.sort { |a,b| a[:instance] - b[:instance] }
138
+ end
139
+
140
+ def app_instances(name)
141
+ check_login_status
142
+ json_get(path(VMC::APPS_PATH, name, "instances"))
143
+ end
144
+
145
+ def app_crashes(name)
146
+ check_login_status
147
+ json_get(path(VMC::APPS_PATH, name, "crashes"))
148
+ end
149
+
150
+ # List the directory or download the actual file indicated by
151
+ # the path.
152
+ def app_files(name, path, instance='0')
153
+ check_login_status
154
+ path = path.gsub('//', '/')
155
+ url = path(VMC::APPS_PATH, name, "instances", instance, "files", path)
156
+ _, body, headers = http_get(url)
157
+ body
158
+ end
159
+
160
+ ######################################################
161
+ # Services
162
+ ######################################################
163
+
164
+ # listing of services that are available in the system
165
+ def services
166
+ check_login_status
167
+ json_get(VMC::SERVICES_PATH)
168
+ end
169
+
170
+ def create_service(service, name)
171
+ check_login_status
172
+ services = services_info
173
+ services ||= []
174
+ service_hash = nil
175
+
176
+ service = service.to_s
177
+
178
+ # FIXME!
179
+ services.each do |service_type, value|
180
+ value.each do |vendor, version|
181
+ version.each do |version_str, service_descr|
182
+ if service == service_descr[:vendor]
183
+ service_hash = {
184
+ :type => service_descr[:type], :tier => 'free',
185
+ :vendor => service, :version => version_str
186
+ }
187
+ break
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ raise TargetError, "Service [#{service}] is not a valid service choice" unless service_hash
194
+ service_hash[:name] = name
195
+ json_post(path(VMC::SERVICES_PATH), service_hash)
196
+ end
197
+
198
+ def delete_service(name)
199
+ check_login_status
200
+ svcs = services || []
201
+ names = svcs.collect { |s| s[:name] }
202
+ raise TargetError, "Service [#{name}] not a valid service" unless names.include? name
203
+ http_delete(path(VMC::SERVICES_PATH, name))
204
+ end
205
+
206
+ def bind_service(service, appname)
207
+ check_login_status
208
+ app = app_info(appname)
209
+ services = app[:services] || []
210
+ app[:services] = services << service
211
+ update_app(appname, app)
212
+ end
213
+
214
+ def unbind_service(service, appname)
215
+ check_login_status
216
+ app = app_info(appname)
217
+ services = app[:services] || []
218
+ services.delete(service)
219
+ app[:services] = services
220
+ update_app(appname, app)
221
+ end
222
+
223
+ ######################################################
224
+ # Resources
225
+ ######################################################
226
+
227
+ # Send in a resources manifest array to the system to have
228
+ # it check what is needed to actually send. Returns array
229
+ # indicating what is needed. This returned manifest should be
230
+ # sent in with the upload if resources were removed.
231
+ # E.g. [{:sha1 => xxx, :size => xxx, :fn => filename}]
232
+ def check_resources(resources)
233
+ check_login_status
234
+ status, body, headers = json_post(VMC::RESOURCES_PATH, resources)
235
+ json_parse(body)
236
+ end
237
+
238
+ ######################################################
239
+ # Validation Helpers
240
+ ######################################################
241
+
242
+ # Checks that the target is valid
243
+ def target_valid?
244
+ return false unless descr = info
245
+ return false unless descr[:name]
246
+ return false unless descr[:build]
247
+ return false unless descr[:version]
248
+ return false unless descr[:support]
249
+ true
250
+ rescue
251
+ false
252
+ end
253
+
254
+ # Checks that the auth_token is valid
255
+ def logged_in?
256
+ descr = info
257
+ if descr
258
+ return false unless descr[:user]
259
+ return false unless descr[:usage]
260
+ @user = descr[:user]
261
+ true
262
+ end
263
+ end
264
+
265
+ ######################################################
266
+ # User login/password
267
+ ######################################################
268
+
269
+ # login and return an auth_token
270
+ # Auth token can be retained and used in creating
271
+ # new clients, avoiding login.
272
+ def login(user, password)
273
+ status, body, headers = json_post(path(VMC::USERS_PATH, user, "tokens"), {:password => password})
274
+ response_info = json_parse(body)
275
+ if response_info
276
+ @user = user
277
+ @auth_token = response_info[:token]
278
+ end
279
+ end
280
+
281
+ # sets the password for the current logged user
282
+ def change_password(new_password)
283
+ check_login_status
284
+ user_info = json_get(path(VMC::USERS_PATH, @user))
285
+ if user_info
286
+ user_info[:password] = new_password
287
+ json_put(path(VMC::USERS_PATH, @user), user_info)
288
+ end
289
+ end
290
+
291
+ ######################################################
292
+ # System administration
293
+ ######################################################
294
+
295
+ def proxy=(proxy)
296
+ @proxy = proxy
297
+ end
298
+
299
+ def proxy_for(proxy)
300
+ @proxy = proxy
301
+ end
302
+
303
+ def users
304
+ check_login_status
305
+ json_get(VMC::USERS_PATH)
306
+ end
307
+
308
+ def add_user(user_email, password)
309
+ json_post(VMC::USERS_PATH, { :email => user_email, :password => password })
310
+ end
311
+
312
+ def delete_user(user_email)
313
+ check_login_status
314
+ http_delete(path(VMC::USERS_PATH, user_email))
315
+ end
316
+
317
+ ######################################################
318
+
319
+ def self.path(*path)
320
+ path.flatten.collect { |x|
321
+ URI.encode x.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
322
+ }.join("/")
323
+ end
324
+
325
+ private
326
+
327
+ def path(*args, &blk)
328
+ self.class.path(*args, &blk)
329
+ end
330
+
331
+ def json_get(url)
332
+ status, body, headers = http_get(url, 'application/json')
333
+ json_parse(body)
334
+ rescue JSON::ParserError
335
+ raise BadResponse, "Can't parse response into JSON", body
336
+ end
337
+
338
+ def json_post(url, payload)
339
+ http_post(url, payload.to_json, 'application/json')
340
+ end
341
+
342
+ def json_put(url, payload)
343
+ http_put(url, payload.to_json, 'application/json')
344
+ end
345
+
346
+ def json_parse(str)
347
+ if str
348
+ JSON.parse(str, :symbolize_names => true)
349
+ end
350
+ end
351
+
352
+ require 'rest_client'
353
+
354
+ # HTTP helpers
355
+
356
+ def http_get(path, content_type=nil)
357
+ request(:get, path, content_type)
358
+ end
359
+
360
+ def http_post(path, body, content_type=nil)
361
+ request(:post, path, content_type, body)
362
+ end
363
+
364
+ def http_put(path, body, content_type=nil)
365
+ request(:put, path, content_type, body)
366
+ end
367
+
368
+ def http_delete(path)
369
+ request(:delete, path)
370
+ end
371
+
372
+ def request(method, path, content_type = nil, payload = nil, headers = {})
373
+ headers = headers.dup
374
+ headers['AUTHORIZATION'] = @auth_token if @auth_token
375
+ headers['PROXY-USER'] = @proxy if @proxy
376
+
377
+ if content_type
378
+ headers['Content-Type'] = content_type
379
+ headers['Accept'] = content_type
380
+ end
381
+
382
+ req = {
383
+ :method => method, :url => "#{@target}/#{path}",
384
+ :payload => payload, :headers => headers, :multipart => true
385
+ }
386
+ status, body, response_headers = perform_http_request(req)
387
+
388
+ if request_failed?(status)
389
+ # FIXME, old cc returned 400 on not found for file access
390
+ err = (status == 404 || status == 400) ? NotFound : TargetError
391
+ raise err, parse_error_message(status, body)
392
+ else
393
+ return status, body, response_headers
394
+ end
395
+ rescue URI::Error, SocketError, Errno::ECONNREFUSED => e
396
+ raise BadTarget, "Cannot access target (%s)" % [ e.message ]
397
+ end
398
+
399
+ def request_failed?(status)
400
+ VMC_HTTP_ERROR_CODES.detect{|error_code| status >= error_code}
401
+ end
402
+
403
+ def perform_http_request(req)
404
+ proxy_uri = URI.parse(req[:url]).find_proxy()
405
+ RestClient.proxy = proxy_uri.to_s if proxy_uri
406
+
407
+ # Setup tracing if needed
408
+ unless trace.nil?
409
+ req[:headers]['X-VCAP-Trace'] = (trace == true ? '22' : trace)
410
+ end
411
+
412
+ result = nil
413
+ RestClient::Request.execute(req) do |response, request|
414
+ result = [ response.code, response.body, response.headers ]
415
+ unless trace.nil?
416
+ puts '>>>'
417
+ puts "PROXY: #{RestClient.proxy}" if RestClient.proxy
418
+ puts "REQUEST: #{req[:method]} #{req[:url]}"
419
+ puts "RESPONSE_HEADERS:"
420
+ response.headers.each do |key, value|
421
+ puts " #{key} : #{value}"
422
+ end
423
+ puts "REQUEST_BODY: #{req[:payload]}" if req[:payload]
424
+ puts "RESPONSE: [#{response.code}]"
425
+ begin
426
+ puts JSON.pretty_generate(JSON.parse(response.body))
427
+ rescue
428
+ puts "#{response.body}"
429
+ end
430
+ puts '<<<'
431
+ end
432
+ end
433
+ result
434
+ rescue Net::HTTPBadResponse => e
435
+ raise BadTarget "Received bad HTTP response from target: #{e}"
436
+ rescue SystemCallError, RestClient::Exception => e
437
+ raise HTTPException, "HTTP exception: #{e.class}:#{e}"
438
+ end
439
+
440
+ def truncate(str, limit = 30)
441
+ etc = '...'
442
+ stripped = str.strip[0..limit]
443
+ if stripped.length > limit
444
+ stripped + etc
445
+ else
446
+ stripped
447
+ end
448
+ end
449
+
450
+ def parse_error_message(status, body)
451
+ parsed_body = json_parse(body.to_s)
452
+ if parsed_body && parsed_body[:code] && parsed_body[:description]
453
+ desc = parsed_body[:description].gsub("\"","'")
454
+ "Error #{parsed_body[:code]}: #{desc}"
455
+ else
456
+ "Error (HTTP #{status}): #{body}"
457
+ end
458
+ rescue JSON::ParserError
459
+ if body.nil? || body.empty?
460
+ "Error (#{status}): No Response Received"
461
+ else
462
+ body_out = trace ? body : truncate(body)
463
+ "Error (JSON #{status}): #{body_out}"
464
+ end
465
+ end
466
+
467
+ def check_login_status
468
+ raise AuthError unless @user || logged_in?
469
+ end
470
+
471
+ end