tac 0.6.0
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.
- data/LICENSE +24 -0
- data/README.md +102 -0
- data/Rakefile +101 -0
- data/bin/tac +6 -0
- data/caldecott_helper/Gemfile +10 -0
- data/caldecott_helper/Gemfile.lock +48 -0
- data/caldecott_helper/server.rb +43 -0
- data/config/clients.yml +17 -0
- data/config/micro/offline.conf +2 -0
- data/config/micro/paths.yml +22 -0
- data/config/micro/refresh_ip.rb +20 -0
- data/lib/cli/commands/admin.rb +108 -0
- data/lib/cli/commands/apps.rb +1133 -0
- data/lib/cli/commands/base.rb +232 -0
- data/lib/cli/commands/manifest.rb +56 -0
- data/lib/cli/commands/micro.rb +115 -0
- data/lib/cli/commands/misc.rb +129 -0
- data/lib/cli/commands/services.rb +242 -0
- data/lib/cli/commands/user.rb +65 -0
- data/lib/cli/config.rb +173 -0
- data/lib/cli/console_helper.rb +160 -0
- data/lib/cli/core_ext.rb +122 -0
- data/lib/cli/errors.rb +19 -0
- data/lib/cli/frameworks.rb +276 -0
- data/lib/cli/manifest_helper.rb +341 -0
- data/lib/cli/runner.rb +547 -0
- data/lib/cli/services_helper.rb +92 -0
- data/lib/cli/tunnel_helper.rb +332 -0
- data/lib/cli/usage.rb +115 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/zip_util.rb +77 -0
- data/lib/cli.rb +47 -0
- data/lib/vmc/client.rb +573 -0
- data/lib/vmc/const.rb +24 -0
- data/lib/vmc/micro/switcher/base.rb +97 -0
- data/lib/vmc/micro/switcher/darwin.rb +19 -0
- data/lib/vmc/micro/switcher/dummy.rb +15 -0
- data/lib/vmc/micro/switcher/linux.rb +16 -0
- data/lib/vmc/micro/switcher/windows.rb +31 -0
- data/lib/vmc/micro/vmrun.rb +158 -0
- data/lib/vmc/micro.rb +56 -0
- data/lib/vmc.rb +3 -0
- metadata +295 -0
data/lib/vmc/client.rb
ADDED
@@ -0,0 +1,573 @@
|
|
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 quota_info
|
57
|
+
# TODO: Should merge for new version IMO, general, services, user_account
|
58
|
+
json_get(VMC::QUOTA_INFO_PATH)
|
59
|
+
end
|
60
|
+
|
61
|
+
def raw_info
|
62
|
+
http_get(VMC::INFO_PATH)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Global listing of services that are available on the target system
|
66
|
+
def services_info
|
67
|
+
check_login_status
|
68
|
+
json_get(path(VMC::GLOBAL_SERVICES_PATH))
|
69
|
+
end
|
70
|
+
|
71
|
+
def runtimes_info
|
72
|
+
json_get(path(VMC::GLOBAL_RUNTIMES_PATH))
|
73
|
+
end
|
74
|
+
|
75
|
+
######################################################
|
76
|
+
# Apps
|
77
|
+
######################################################
|
78
|
+
|
79
|
+
def apps
|
80
|
+
check_login_status
|
81
|
+
json_get(VMC::APPS_PATH)
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_app(name, manifest={})
|
85
|
+
check_login_status
|
86
|
+
app = manifest.dup
|
87
|
+
app[:name] = name
|
88
|
+
app[:instances] ||= 1
|
89
|
+
json_post(VMC::APPS_PATH, app)
|
90
|
+
end
|
91
|
+
|
92
|
+
def update_app(name, manifest)
|
93
|
+
check_login_status
|
94
|
+
json_put(path(VMC::APPS_PATH, name), manifest)
|
95
|
+
end
|
96
|
+
|
97
|
+
def upload_app(name, zipfile, resource_manifest=nil)
|
98
|
+
#FIXME, manifest should be allowed to be null, here for compatability with old cc's
|
99
|
+
resource_manifest ||= []
|
100
|
+
check_login_status
|
101
|
+
upload_data = {:_method => 'put'}
|
102
|
+
if zipfile
|
103
|
+
if zipfile.is_a? File
|
104
|
+
file = zipfile
|
105
|
+
else
|
106
|
+
file = File.new(zipfile, 'rb')
|
107
|
+
end
|
108
|
+
upload_data[:application] = file
|
109
|
+
end
|
110
|
+
upload_data[:resources] = resource_manifest.to_json if resource_manifest
|
111
|
+
http_post(path(VMC::APPS_PATH, name, "application"), upload_data)
|
112
|
+
rescue RestClient::ServerBrokeConnection
|
113
|
+
retry
|
114
|
+
end
|
115
|
+
|
116
|
+
def delete_app(name)
|
117
|
+
check_login_status
|
118
|
+
http_delete(path(VMC::APPS_PATH, name))
|
119
|
+
end
|
120
|
+
|
121
|
+
def app_info(name)
|
122
|
+
check_login_status
|
123
|
+
json_get(path(VMC::APPS_PATH, name))
|
124
|
+
end
|
125
|
+
|
126
|
+
def app_update_info(name)
|
127
|
+
check_login_status
|
128
|
+
json_get(path(VMC::APPS_PATH, name, "update"))
|
129
|
+
end
|
130
|
+
|
131
|
+
def app_stats(name)
|
132
|
+
check_login_status
|
133
|
+
stats_raw = json_get(path(VMC::APPS_PATH, name, "stats"))
|
134
|
+
stats = []
|
135
|
+
stats_raw.each_pair do |k, entry|
|
136
|
+
# Skip entries with no stats
|
137
|
+
next unless entry[:stats]
|
138
|
+
entry[:instance] = k.to_s.to_i
|
139
|
+
entry[:state] = entry[:state].to_sym if entry[:state]
|
140
|
+
stats << entry
|
141
|
+
end
|
142
|
+
stats.sort { |a,b| a[:instance] - b[:instance] }
|
143
|
+
end
|
144
|
+
|
145
|
+
def app_instances(name)
|
146
|
+
check_login_status
|
147
|
+
json_get(path(VMC::APPS_PATH, name, "instances"))
|
148
|
+
end
|
149
|
+
|
150
|
+
def app_crashes(name)
|
151
|
+
check_login_status
|
152
|
+
json_get(path(VMC::APPS_PATH, name, "crashes"))
|
153
|
+
end
|
154
|
+
|
155
|
+
# List the directory or download the actual file indicated by
|
156
|
+
# the path.
|
157
|
+
def app_files(name, path, instance='0')
|
158
|
+
check_login_status
|
159
|
+
path = path.gsub('//', '/')
|
160
|
+
url = path(VMC::APPS_PATH, name, "instances", instance, "files", path)
|
161
|
+
_, body, headers = http_get(url)
|
162
|
+
body
|
163
|
+
end
|
164
|
+
|
165
|
+
######################################################
|
166
|
+
# Services
|
167
|
+
######################################################
|
168
|
+
|
169
|
+
# listing of services that are available in the system
|
170
|
+
def services
|
171
|
+
check_login_status
|
172
|
+
json_get(VMC::SERVICES_PATH)
|
173
|
+
end
|
174
|
+
|
175
|
+
=begin
|
176
|
+
def create_service(service, name)
|
177
|
+
check_login_status
|
178
|
+
services = services_info
|
179
|
+
services ||= []
|
180
|
+
service_hash = nil
|
181
|
+
|
182
|
+
service = service.to_s
|
183
|
+
|
184
|
+
# FIXME!
|
185
|
+
services.each do |service_type, value|
|
186
|
+
value.each do |vendor, version|
|
187
|
+
version.each do |version_str, service_descr|
|
188
|
+
if service == service_descr[:vendor]
|
189
|
+
service_hash = {
|
190
|
+
:type => service_descr[:type], :tier => 'free',
|
191
|
+
:vendor => service, :version => version_str
|
192
|
+
}
|
193
|
+
break
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
raise TargetError, "Service [#{service}] is not a valid service choice" unless service_hash
|
200
|
+
service_hash[:name] = name
|
201
|
+
json_post(path(VMC::SERVICES_PATH), service_hash)
|
202
|
+
end
|
203
|
+
=end
|
204
|
+
|
205
|
+
def create_service(service, name, dbflag , dbjndi, dbusername, dbpasswd, dburl, dbsize)
|
206
|
+
|
207
|
+
check_login_status
|
208
|
+
services = services_info
|
209
|
+
services ||= []
|
210
|
+
service_hash = nil
|
211
|
+
|
212
|
+
service = service.to_s
|
213
|
+
|
214
|
+
# FIXME!
|
215
|
+
services.each do |service_type, value|
|
216
|
+
value.each do |vendor, version|
|
217
|
+
version.each do |version_str, service_descr|
|
218
|
+
if service == service_descr[:vendor]
|
219
|
+
service_hash = {
|
220
|
+
:type => service_descr[:type], :tier => 'free',
|
221
|
+
:vendor => service, :version => version_str
|
222
|
+
}
|
223
|
+
break
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
raise TargetError, "Service [#{service}] is not a valid service choice" unless service_hash
|
230
|
+
service_hash[:name] = name
|
231
|
+
service_hash[:db_flag] = dbflag
|
232
|
+
service_hash[:db_jndi] = dbjndi
|
233
|
+
service_hash[:db_uname] = dbusername
|
234
|
+
service_hash[:db_passwd] = dbpasswd
|
235
|
+
service_hash[:db_url] = dburl
|
236
|
+
service_hash[:max_db_size] = dbsize
|
237
|
+
#puts service_hash
|
238
|
+
json_post(path(VMC::SERVICES_PATH), service_hash)
|
239
|
+
end
|
240
|
+
|
241
|
+
def delete_service(name)
|
242
|
+
check_login_status
|
243
|
+
svcs = services || []
|
244
|
+
names = svcs.collect { |s| s[:name] }
|
245
|
+
raise TargetError, "Service [#{name}] not a valid service" unless names.include? name
|
246
|
+
http_delete(path(VMC::SERVICES_PATH, name))
|
247
|
+
end
|
248
|
+
|
249
|
+
def check_quota(name)
|
250
|
+
check_login_status
|
251
|
+
svcs = services || []
|
252
|
+
type = nil;
|
253
|
+
max_db_size = 0
|
254
|
+
names = svcs.collect { |s|
|
255
|
+
if name == s[:name]
|
256
|
+
max_db_size = s[:max_db_size]
|
257
|
+
type = s[:vendor]
|
258
|
+
end
|
259
|
+
s[:name]
|
260
|
+
}
|
261
|
+
raise TargetError, "Temporary does not support" unless "mysql" == type
|
262
|
+
raise TargetError, "Service [#{name}] not a valid service" unless names.include? name
|
263
|
+
max_db_size
|
264
|
+
end
|
265
|
+
|
266
|
+
def quota_service(name, dbsize, update_flag)
|
267
|
+
service_hash ={
|
268
|
+
:max_db_size => dbsize
|
269
|
+
}
|
270
|
+
service_hash[:update_flag] = update_flag
|
271
|
+
#puts service_hash
|
272
|
+
http_put(path(VMC::SERVICES_PATH, name), service_hash)
|
273
|
+
end
|
274
|
+
|
275
|
+
def bind_service(service, appname)
|
276
|
+
check_login_status
|
277
|
+
app = app_info(appname)
|
278
|
+
services = app[:services] || []
|
279
|
+
app[:services] = services << service
|
280
|
+
update_app(appname, app)
|
281
|
+
end
|
282
|
+
|
283
|
+
def unbind_service(service, appname)
|
284
|
+
check_login_status
|
285
|
+
app = app_info(appname)
|
286
|
+
services = app[:services] || []
|
287
|
+
services.delete(service)
|
288
|
+
app[:services] = services
|
289
|
+
update_app(appname, app)
|
290
|
+
end
|
291
|
+
|
292
|
+
######################################################
|
293
|
+
# Resources
|
294
|
+
######################################################
|
295
|
+
|
296
|
+
# Send in a resources manifest array to the system to have
|
297
|
+
# it check what is needed to actually send. Returns array
|
298
|
+
# indicating what is needed. This returned manifest should be
|
299
|
+
# sent in with the upload if resources were removed.
|
300
|
+
# E.g. [{:sha1 => xxx, :size => xxx, :fn => filename}]
|
301
|
+
def check_resources(resources)
|
302
|
+
check_login_status
|
303
|
+
status, body, headers = json_post(VMC::RESOURCES_PATH, resources)
|
304
|
+
json_parse(body)
|
305
|
+
end
|
306
|
+
|
307
|
+
######################################################
|
308
|
+
# Validation Helpers
|
309
|
+
######################################################
|
310
|
+
|
311
|
+
# Checks that the target is valid
|
312
|
+
def target_valid?
|
313
|
+
return false unless descr = info
|
314
|
+
return false unless descr[:name]
|
315
|
+
return false unless descr[:build]
|
316
|
+
return false unless descr[:version]
|
317
|
+
return false unless descr[:support]
|
318
|
+
true
|
319
|
+
rescue
|
320
|
+
false
|
321
|
+
end
|
322
|
+
|
323
|
+
# Checks that the auth_token is valid
|
324
|
+
def logged_in?
|
325
|
+
descr = info
|
326
|
+
if descr
|
327
|
+
return false unless descr[:user]
|
328
|
+
return false unless descr[:usage]
|
329
|
+
@user = descr[:user]
|
330
|
+
true
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
######################################################
|
335
|
+
# User login/password
|
336
|
+
######################################################
|
337
|
+
|
338
|
+
# login and return an auth_token
|
339
|
+
# Auth token can be retained and used in creating
|
340
|
+
# new clients, avoiding login.
|
341
|
+
def login(user, password)
|
342
|
+
status, body, headers = json_post(path(VMC::USERS_PATH, user, "tokens"), {:password => password})
|
343
|
+
response_info = json_parse(body)
|
344
|
+
if response_info
|
345
|
+
@user = user
|
346
|
+
@auth_token = response_info[:token]
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
# sets the password for the current logged user
|
351
|
+
def change_password(new_password)
|
352
|
+
check_login_status
|
353
|
+
user_info = json_get(path(VMC::USERS_PATH, @user))
|
354
|
+
#puts "changepasswd"
|
355
|
+
#puts user_info
|
356
|
+
if user_info
|
357
|
+
user_info[:password] = new_password
|
358
|
+
# puts path(VMC::USERS_PATH, @user)
|
359
|
+
json_put(path(VMC::USERS_PATH, @user), user_info)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def get_user_info(email)
|
364
|
+
check_login_status
|
365
|
+
user_info = json_get(path(VMC::USERSQUOTA_PATH, email))
|
366
|
+
end
|
367
|
+
|
368
|
+
# sets the password for the current logged user
|
369
|
+
def change_quota(mem, apps, uris, services, user_info, email)
|
370
|
+
check_login_status
|
371
|
+
#user_info = json_get(path(VMC::USERS_PATH, @user))
|
372
|
+
#if user_info
|
373
|
+
user_info[:mem] = mem
|
374
|
+
user_info[:app] = apps
|
375
|
+
user_info[:uri] = uris
|
376
|
+
user_info[:service] = services
|
377
|
+
user_info[:email] = email
|
378
|
+
json_put(path(VMC::USERSQUOTA_PATH, user_info), user_info)
|
379
|
+
#end
|
380
|
+
end
|
381
|
+
|
382
|
+
######################################################
|
383
|
+
# System administration
|
384
|
+
######################################################
|
385
|
+
|
386
|
+
|
387
|
+
def proxy=(proxy)
|
388
|
+
@proxy = proxy
|
389
|
+
end
|
390
|
+
|
391
|
+
def proxy_for(proxy)
|
392
|
+
@proxy = proxy
|
393
|
+
end
|
394
|
+
|
395
|
+
def users
|
396
|
+
check_login_status
|
397
|
+
json_get(VMC::USERS_PATH)
|
398
|
+
end
|
399
|
+
|
400
|
+
# def add_user(user_email, password, app)
|
401
|
+
# json_post(VMC::USERS_PATH, { :email => user_email, :password => password, :app => app})
|
402
|
+
#end
|
403
|
+
|
404
|
+
def add_user(user_email, password)
|
405
|
+
json_post(VMC::USERS_PATH, { :email => user_email, :password => password })
|
406
|
+
end
|
407
|
+
|
408
|
+
def delete_user(user_email)
|
409
|
+
check_login_status
|
410
|
+
http_delete(path(VMC::USERS_PATH, user_email))
|
411
|
+
end
|
412
|
+
|
413
|
+
######################################################
|
414
|
+
|
415
|
+
def self.path(*path)
|
416
|
+
path.flatten.collect { |x|
|
417
|
+
URI.encode x.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
|
418
|
+
}.join("/")
|
419
|
+
end
|
420
|
+
|
421
|
+
private
|
422
|
+
|
423
|
+
def path(*args, &blk)
|
424
|
+
self.class.path(*args, &blk)
|
425
|
+
end
|
426
|
+
|
427
|
+
def json_get(url)
|
428
|
+
status, body, headers = http_get(url, 'application/json')
|
429
|
+
json_parse(body)
|
430
|
+
rescue JSON::ParserError
|
431
|
+
raise BadResponse, "Can't parse response into JSON", body
|
432
|
+
end
|
433
|
+
|
434
|
+
def json_post(url, payload)
|
435
|
+
http_post(url, payload.to_json, 'application/json')
|
436
|
+
end
|
437
|
+
|
438
|
+
def json_put(url, payload)
|
439
|
+
#puts url
|
440
|
+
http_put(url, payload.to_json, 'application/json')
|
441
|
+
end
|
442
|
+
|
443
|
+
def json_parse(str)
|
444
|
+
if str
|
445
|
+
JSON.parse(str, :symbolize_names => true)
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
require 'rest_client'
|
450
|
+
|
451
|
+
# HTTP helpers
|
452
|
+
|
453
|
+
def http_get(path, content_type=nil)
|
454
|
+
request(:get, path, content_type)
|
455
|
+
end
|
456
|
+
|
457
|
+
def http_post(path, body, content_type=nil)
|
458
|
+
#puts 4
|
459
|
+
request(:post, path, content_type, body)
|
460
|
+
end
|
461
|
+
|
462
|
+
def http_put(path, body, content_type=nil)
|
463
|
+
request(:put, path, content_type, body)
|
464
|
+
end
|
465
|
+
|
466
|
+
def http_delete(path)
|
467
|
+
request(:delete, path)
|
468
|
+
end
|
469
|
+
|
470
|
+
def request(method, path, content_type = nil, payload = nil, headers = {})
|
471
|
+
|
472
|
+
#puts 5
|
473
|
+
#puts path
|
474
|
+
headers = headers.dup
|
475
|
+
headers['AUTHORIZATION'] = @auth_token if @auth_token
|
476
|
+
headers['PROXY-USER'] = @proxy if @proxy
|
477
|
+
|
478
|
+
if content_type
|
479
|
+
headers['Content-Type'] = content_type
|
480
|
+
headers['Accept'] = content_type
|
481
|
+
end
|
482
|
+
|
483
|
+
req = {
|
484
|
+
:method => method, :url => "#{@target}/#{path}",
|
485
|
+
:payload => payload, :headers => headers, :multipart => true
|
486
|
+
}
|
487
|
+
#puts req
|
488
|
+
status, body, response_headers = perform_http_request(req)
|
489
|
+
|
490
|
+
if request_failed?(status)
|
491
|
+
# FIXME, old cc returned 400 on not found for file access
|
492
|
+
err = (status == 404 || status == 400) ? NotFound : TargetError
|
493
|
+
raise err, parse_error_message(status, body)
|
494
|
+
else
|
495
|
+
return status, body, response_headers
|
496
|
+
end
|
497
|
+
rescue URI::Error, SocketError, Errno::ECONNREFUSED => e
|
498
|
+
raise BadTarget, "Cannot access target (%s)" % [ e.message ]
|
499
|
+
end
|
500
|
+
|
501
|
+
def request_failed?(status)
|
502
|
+
VMC_HTTP_ERROR_CODES.detect{|error_code| status >= error_code}
|
503
|
+
end
|
504
|
+
|
505
|
+
def perform_http_request(req)
|
506
|
+
proxy_uri = URI.parse(req[:url]).find_proxy()
|
507
|
+
RestClient.proxy = proxy_uri.to_s if proxy_uri
|
508
|
+
|
509
|
+
# Setup tracing if needed
|
510
|
+
unless trace.nil?
|
511
|
+
req[:headers]['X-VCAP-Trace'] = (trace == true ? '22' : trace)
|
512
|
+
end
|
513
|
+
|
514
|
+
result = nil
|
515
|
+
RestClient::Request.execute(req) do |response, request|
|
516
|
+
result = [ response.code, response.body, response.headers ]
|
517
|
+
unless trace.nil?
|
518
|
+
puts '>>>'
|
519
|
+
puts "PROXY: #{RestClient.proxy}" if RestClient.proxy
|
520
|
+
puts "REQUEST: #{req[:method]} #{req[:url]}"
|
521
|
+
puts "RESPONSE_HEADERS:"
|
522
|
+
response.headers.each do |key, value|
|
523
|
+
puts " #{key} : #{value}"
|
524
|
+
end
|
525
|
+
puts "REQUEST_BODY: #{req[:payload]}" if req[:payload]
|
526
|
+
puts "RESPONSE: [#{response.code}]"
|
527
|
+
begin
|
528
|
+
puts JSON.pretty_generate(JSON.parse(response.body))
|
529
|
+
rescue
|
530
|
+
puts "#{response.body}"
|
531
|
+
end
|
532
|
+
puts '<<<'
|
533
|
+
end
|
534
|
+
end
|
535
|
+
result
|
536
|
+
rescue Net::HTTPBadResponse => e
|
537
|
+
raise BadTarget "Received bad HTTP response from target: #{e}"
|
538
|
+
rescue SystemCallError, RestClient::Exception => e
|
539
|
+
raise HTTPException, "HTTP exception: #{e.class}:#{e}"
|
540
|
+
end
|
541
|
+
|
542
|
+
def truncate(str, limit = 30)
|
543
|
+
etc = '...'
|
544
|
+
stripped = str.strip[0..limit]
|
545
|
+
if stripped.length > limit
|
546
|
+
stripped + etc
|
547
|
+
else
|
548
|
+
stripped
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
def parse_error_message(status, body)
|
553
|
+
parsed_body = json_parse(body.to_s)
|
554
|
+
if parsed_body && parsed_body[:code] && parsed_body[:description]
|
555
|
+
desc = parsed_body[:description].gsub("\"","'")
|
556
|
+
"Error #{parsed_body[:code]}: #{desc}"
|
557
|
+
else
|
558
|
+
"Error (HTTP #{status}): #{body}"
|
559
|
+
end
|
560
|
+
rescue JSON::ParserError
|
561
|
+
if body.nil? || body.empty?
|
562
|
+
"Error (#{status}): No Response Received"
|
563
|
+
else
|
564
|
+
body_out = trace ? body : truncate(body)
|
565
|
+
"Error (JSON #{status}): #{body_out}"
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
def check_login_status
|
570
|
+
raise AuthError unless @user || logged_in?
|
571
|
+
end
|
572
|
+
|
573
|
+
end
|
data/lib/vmc/const.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module VMC
|
2
|
+
|
3
|
+
# This is the internal VMC version number, and is not necessarily
|
4
|
+
# the same as the RubyGem version (VMC::Cli::VERSION).
|
5
|
+
VERSION = '0.3.2'
|
6
|
+
|
7
|
+
# Targets
|
8
|
+
DEFAULT_TARGET = 'https://api.cloudfoundry.com'
|
9
|
+
DEFAULT_LOCAL_TARGET = 'http://api.vcap.me'
|
10
|
+
|
11
|
+
# General Paths
|
12
|
+
INFO_PATH = 'info'
|
13
|
+
QUOTA_INFO_PATH = 'quotaInfo'
|
14
|
+
GLOBAL_SERVICES_PATH = ['info', 'services']
|
15
|
+
GLOBAL_RUNTIMES_PATH = ['info', 'runtimes']
|
16
|
+
RESOURCES_PATH = 'resources'
|
17
|
+
|
18
|
+
# User specific paths
|
19
|
+
APPS_PATH = 'apps'
|
20
|
+
SERVICES_PATH = 'services'
|
21
|
+
USERS_PATH = 'users'
|
22
|
+
USERSQUOTA_PATH = 'usersQuota'
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'interact'
|
2
|
+
|
3
|
+
module VMC::Micro::Switcher
|
4
|
+
class Base
|
5
|
+
include Interactive
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
|
10
|
+
@vmrun = VMC::Micro::VMrun.new(config)
|
11
|
+
unless @vmrun.running?
|
12
|
+
if ask("Micro Cloud Foundry VM is not running. Do you want to start it?", :choices => ['y', 'n']) == 'y'
|
13
|
+
display "Starting Micro Cloud Foundry VM: ", false
|
14
|
+
@vmrun.start
|
15
|
+
say "done".green
|
16
|
+
else
|
17
|
+
err "Micro Cloud Foundry VM needs to be running."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
err "Micro Cloud Foundry VM initial setup needs to be completed before using 'vmc micro'" unless @vmrun.ready?
|
22
|
+
end
|
23
|
+
|
24
|
+
def offline
|
25
|
+
unless @vmrun.offline?
|
26
|
+
# save online connection type so we can restore it later
|
27
|
+
@config['online_connection_type'] = @vmrun.connection_type
|
28
|
+
|
29
|
+
if (@config['online_connection_type'] != 'nat')
|
30
|
+
if ask("Reconfigure Micro Cloud Foundry VM network to nat mode and reboot?", :choices => ['y', 'n']) == 'y'
|
31
|
+
display "Rebooting Micro Cloud Foundry VM: ", false
|
32
|
+
@vmrun.connection_type = 'nat'
|
33
|
+
@vmrun.reset
|
34
|
+
say "done".green
|
35
|
+
else
|
36
|
+
err "Aborted"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
display "Setting Micro Cloud Foundry VM to offline mode: ", false
|
41
|
+
@vmrun.offline!
|
42
|
+
say "done".green
|
43
|
+
display "Setting host DNS server: ", false
|
44
|
+
|
45
|
+
@config['domain'] = @vmrun.domain
|
46
|
+
@config['ip'] = @vmrun.ip
|
47
|
+
set_nameserver(@config['domain'], @config['ip'])
|
48
|
+
say "done".green
|
49
|
+
else
|
50
|
+
say "Micro Cloud Foundry VM already in offline mode".yellow
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def online
|
55
|
+
if @vmrun.offline?
|
56
|
+
current_connection_type = @vmrun.connection_type
|
57
|
+
@config['online_connection_type'] ||= current_connection_type
|
58
|
+
|
59
|
+
if (@config['online_connection_type'] != current_connection_type)
|
60
|
+
# TODO handle missing connection type in saved config
|
61
|
+
question = "Reconfigure Micro Cloud Foundry VM network to #{@config['online_connection_type']} mode and reboot?"
|
62
|
+
if ask(question, :choices => ['y', 'n']) == 'y'
|
63
|
+
display "Rebooting Micro Cloud Foundry VM: ", false
|
64
|
+
@vmrun.connection_type = @config['online_connection_type']
|
65
|
+
@vmrun.reset
|
66
|
+
say "done".green
|
67
|
+
else
|
68
|
+
err "Aborted"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
display "Unsetting host DNS server: ", false
|
73
|
+
# TODO handle missing domain and ip in saved config (look at the VM)
|
74
|
+
@config['domain'] ||= @vmrun.domain
|
75
|
+
@config['ip'] ||= @vmrun.ip
|
76
|
+
unset_nameserver(@config['domain'], @config['ip'])
|
77
|
+
say "done".green
|
78
|
+
|
79
|
+
display "Setting Micro Cloud Foundry VM to online mode: ", false
|
80
|
+
@vmrun.online!
|
81
|
+
say "done".green
|
82
|
+
else
|
83
|
+
say "Micro Cloud Foundry already in online mode".yellow
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def status
|
88
|
+
mode = @vmrun.offline? ? 'offline' : 'online'
|
89
|
+
say "Micro Cloud Foundry VM currently in #{mode.green} mode"
|
90
|
+
# should the VMX path be unescaped?
|
91
|
+
say "VMX Path: #{@vmrun.vmx}"
|
92
|
+
say "Domain: #{@vmrun.domain.green}"
|
93
|
+
say "IP Address: #{@vmrun.ip.green}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module VMC::Micro::Switcher
|
2
|
+
|
3
|
+
class Darwin < Base
|
4
|
+
def adminrun(command)
|
5
|
+
VMC::Micro.run_command("osascript", "-e 'do shell script \"#{command}\" with administrator privileges'")
|
6
|
+
end
|
7
|
+
|
8
|
+
def set_nameserver(domain, ip)
|
9
|
+
File.open("/tmp/#{domain}", 'w') { |file| file.write("nameserver #{ip}") }
|
10
|
+
adminrun("mkdir -p /etc/resolver;mv /tmp/#{domain} /etc/resolver/")
|
11
|
+
end
|
12
|
+
|
13
|
+
def unset_nameserver(domain, ip)
|
14
|
+
err "domain missing" unless domain
|
15
|
+
adminrun("rm -f /etc/resolver/#{domain}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|