vmcu 0.3.17
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 +160 -0
- data/Rakefile +101 -0
- data/bin/vmcu +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.rb +47 -0
- data/lib/cli/commands/admin.rb +80 -0
- data/lib/cli/commands/apps.rb +1128 -0
- data/lib/cli/commands/base.rb +238 -0
- data/lib/cli/commands/manifest.rb +56 -0
- data/lib/cli/commands/micro.rb +115 -0
- data/lib/cli/commands/misc.rb +277 -0
- data/lib/cli/commands/services.rb +180 -0
- data/lib/cli/commands/user.rb +96 -0
- data/lib/cli/config.rb +192 -0
- data/lib/cli/console_helper.rb +157 -0
- data/lib/cli/core_ext.rb +122 -0
- data/lib/cli/errors.rb +19 -0
- data/lib/cli/frameworks.rb +244 -0
- data/lib/cli/manifest_helper.rb +302 -0
- data/lib/cli/runner.rb +543 -0
- data/lib/cli/services_helper.rb +84 -0
- data/lib/cli/tunnel_helper.rb +332 -0
- data/lib/cli/usage.rb +118 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/zip_util.rb +77 -0
- data/lib/vmc.rb +3 -0
- data/lib/vmc/client.rb +591 -0
- data/lib/vmc/const.rb +22 -0
- data/lib/vmc/micro.rb +56 -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
- metadata +263 -0
data/lib/cli/version.rb
ADDED
data/lib/cli/zip_util.rb
ADDED
@@ -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/lib/vmc.rb
ADDED
data/lib/vmc/client.rb
ADDED
@@ -0,0 +1,591 @@
|
|
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, :via_uhuru_cloud, :cloud_team, :proxy_realm
|
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
|
+
@via_uhuru_cloud = false
|
45
|
+
end
|
46
|
+
|
47
|
+
######################################################
|
48
|
+
# Target info
|
49
|
+
######################################################
|
50
|
+
|
51
|
+
# Retrieves information on the target cloud, and optionally the logged in user
|
52
|
+
def info
|
53
|
+
# TODO: Should merge for new version IMO, general, services, user_account
|
54
|
+
json_get(VMC::INFO_PATH)
|
55
|
+
end
|
56
|
+
|
57
|
+
def raw_info
|
58
|
+
http_get(VMC::INFO_PATH)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Global listing of services that are available on the target system
|
62
|
+
def services_info
|
63
|
+
check_login_status
|
64
|
+
json_get(path(VMC::GLOBAL_SERVICES_PATH))
|
65
|
+
end
|
66
|
+
|
67
|
+
def runtimes_info
|
68
|
+
json_get(path(VMC::GLOBAL_RUNTIMES_PATH))
|
69
|
+
end
|
70
|
+
|
71
|
+
######################################################
|
72
|
+
# Apps
|
73
|
+
######################################################
|
74
|
+
|
75
|
+
def apps
|
76
|
+
check_login_status
|
77
|
+
json_get(VMC::APPS_PATH)
|
78
|
+
end
|
79
|
+
|
80
|
+
def create_app(name, manifest={})
|
81
|
+
check_login_status
|
82
|
+
app = manifest.dup
|
83
|
+
app[:name] = name
|
84
|
+
app[:instances] ||= 1
|
85
|
+
json_post(VMC::APPS_PATH, app)
|
86
|
+
end
|
87
|
+
|
88
|
+
def update_app(name, manifest)
|
89
|
+
check_login_status
|
90
|
+
json_put(path(VMC::APPS_PATH, name), manifest)
|
91
|
+
end
|
92
|
+
|
93
|
+
def upload_app(name, zipfile, resource_manifest=nil)
|
94
|
+
#FIXME, manifest should be allowed to be null, here for compatability with old cc's
|
95
|
+
resource_manifest ||= []
|
96
|
+
check_login_status
|
97
|
+
upload_data = {:_method => 'put'}
|
98
|
+
if zipfile
|
99
|
+
if zipfile.is_a? File
|
100
|
+
file = zipfile
|
101
|
+
else
|
102
|
+
file = File.new(zipfile, 'rb')
|
103
|
+
end
|
104
|
+
upload_data[:application] = file
|
105
|
+
end
|
106
|
+
upload_data[:resources] = resource_manifest.to_json if resource_manifest
|
107
|
+
http_post(path(VMC::APPS_PATH, name, "application"), upload_data)
|
108
|
+
rescue RestClient::ServerBrokeConnection
|
109
|
+
retry
|
110
|
+
end
|
111
|
+
|
112
|
+
def delete_app(name)
|
113
|
+
check_login_status
|
114
|
+
http_delete(path(VMC::APPS_PATH, name))
|
115
|
+
end
|
116
|
+
|
117
|
+
def app_info(name)
|
118
|
+
check_login_status
|
119
|
+
json_get(path(VMC::APPS_PATH, name))
|
120
|
+
end
|
121
|
+
|
122
|
+
def app_update_info(name)
|
123
|
+
check_login_status
|
124
|
+
json_get(path(VMC::APPS_PATH, name, "update"))
|
125
|
+
end
|
126
|
+
|
127
|
+
def app_stats(name)
|
128
|
+
check_login_status
|
129
|
+
stats_raw = json_get(path(VMC::APPS_PATH, name, "stats"))
|
130
|
+
stats = []
|
131
|
+
stats_raw.each_pair do |k, entry|
|
132
|
+
# Skip entries with no stats
|
133
|
+
next unless entry[:stats]
|
134
|
+
entry[:instance] = k.to_s.to_i
|
135
|
+
entry[:state] = entry[:state].to_sym if entry[:state]
|
136
|
+
stats << entry
|
137
|
+
end
|
138
|
+
stats.sort { |a,b| a[:instance] - b[:instance] }
|
139
|
+
end
|
140
|
+
|
141
|
+
def app_instances(name)
|
142
|
+
check_login_status
|
143
|
+
json_get(path(VMC::APPS_PATH, name, "instances"))
|
144
|
+
end
|
145
|
+
|
146
|
+
def app_crashes(name)
|
147
|
+
check_login_status
|
148
|
+
json_get(path(VMC::APPS_PATH, name, "crashes"))
|
149
|
+
end
|
150
|
+
|
151
|
+
# List the directory or download the actual file indicated by
|
152
|
+
# the path.
|
153
|
+
def app_files(name, path, instance='0')
|
154
|
+
check_login_status
|
155
|
+
path = path.gsub('//', '/')
|
156
|
+
url = path(VMC::APPS_PATH, name, "instances", instance, "files", path)
|
157
|
+
_, body, headers = http_get(url)
|
158
|
+
body
|
159
|
+
end
|
160
|
+
|
161
|
+
######################################################
|
162
|
+
# Services
|
163
|
+
######################################################
|
164
|
+
|
165
|
+
# listing of services that are available in the system
|
166
|
+
def services
|
167
|
+
check_login_status
|
168
|
+
json_get(VMC::SERVICES_PATH)
|
169
|
+
end
|
170
|
+
|
171
|
+
def create_service(service, name)
|
172
|
+
check_login_status
|
173
|
+
services = services_info
|
174
|
+
services ||= []
|
175
|
+
service_hash = nil
|
176
|
+
|
177
|
+
service = service.to_s
|
178
|
+
|
179
|
+
# FIXME!
|
180
|
+
services.each do |service_type, value|
|
181
|
+
value.each do |vendor, version|
|
182
|
+
version.each do |version_str, service_descr|
|
183
|
+
if service == service_descr[:vendor]
|
184
|
+
service_hash = {
|
185
|
+
:type => service_descr[:type], :tier => 'free',
|
186
|
+
:vendor => service, :version => version_str
|
187
|
+
}
|
188
|
+
break
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
raise TargetError, "Service [#{service}] is not a valid service choice" unless service_hash
|
195
|
+
service_hash[:name] = name
|
196
|
+
json_post(path(VMC::SERVICES_PATH), service_hash)
|
197
|
+
end
|
198
|
+
|
199
|
+
def delete_service(name)
|
200
|
+
check_login_status
|
201
|
+
svcs = services || []
|
202
|
+
names = svcs.collect { |s| s[:name] }
|
203
|
+
raise TargetError, "Service [#{name}] not a valid service" unless names.include? name
|
204
|
+
http_delete(path(VMC::SERVICES_PATH, name))
|
205
|
+
end
|
206
|
+
|
207
|
+
def bind_service(service, appname)
|
208
|
+
check_login_status
|
209
|
+
app = app_info(appname)
|
210
|
+
services = app[:services] || []
|
211
|
+
app[:services] = services << service
|
212
|
+
update_app(appname, app)
|
213
|
+
end
|
214
|
+
|
215
|
+
def unbind_service(service, appname)
|
216
|
+
check_login_status
|
217
|
+
app = app_info(appname)
|
218
|
+
services = app[:services] || []
|
219
|
+
services.delete(service)
|
220
|
+
app[:services] = services
|
221
|
+
update_app(appname, app)
|
222
|
+
end
|
223
|
+
|
224
|
+
######################################################
|
225
|
+
# Resources
|
226
|
+
######################################################
|
227
|
+
|
228
|
+
# Send in a resources manifest array to the system to have
|
229
|
+
# it check what is needed to actually send. Returns array
|
230
|
+
# indicating what is needed. This returned manifest should be
|
231
|
+
# sent in with the upload if resources were removed.
|
232
|
+
# E.g. [{:sha1 => xxx, :size => xxx, :fn => filename}]
|
233
|
+
def check_resources(resources)
|
234
|
+
check_login_status
|
235
|
+
status, body, headers = json_post(VMC::RESOURCES_PATH, resources)
|
236
|
+
json_parse(body)
|
237
|
+
end
|
238
|
+
|
239
|
+
######################################################
|
240
|
+
# Validation Helpers
|
241
|
+
######################################################
|
242
|
+
|
243
|
+
# currently not used, kept just for reference
|
244
|
+
def generic_target_valid?
|
245
|
+
@via_uhuru_cloud = false
|
246
|
+
return true if target_valid?
|
247
|
+
|
248
|
+
@via_uhuru_cloud = true
|
249
|
+
return true if uhuru_target_valid?
|
250
|
+
|
251
|
+
@via_uhuru_cloud = false
|
252
|
+
false
|
253
|
+
end
|
254
|
+
|
255
|
+
# Checks that the target is valid
|
256
|
+
def target_valid?
|
257
|
+
return false unless descr = info
|
258
|
+
return false unless descr[:name]
|
259
|
+
return false unless descr[:build]
|
260
|
+
return false unless descr[:version]
|
261
|
+
return false unless descr[:support]
|
262
|
+
true
|
263
|
+
rescue
|
264
|
+
false
|
265
|
+
end
|
266
|
+
|
267
|
+
def uhuru_target_valid?
|
268
|
+
# return false unless !via_uhuru_cloud || get_cloud_domain
|
269
|
+
return false unless uhuru_version =~ /Uhuru Cloud API, version = \d+.\d+.\d+.\d+/
|
270
|
+
true
|
271
|
+
rescue
|
272
|
+
false
|
273
|
+
end
|
274
|
+
|
275
|
+
# checks if the target is a direct Cloud Foundry target or Uhuru target
|
276
|
+
def determine_target_type
|
277
|
+
if uhuru_target_valid?
|
278
|
+
@via_uhuru_cloud = true
|
279
|
+
else
|
280
|
+
@via_uhuru_cloud = false
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
# Checks that the auth_token is valid
|
286
|
+
def logged_in?
|
287
|
+
descr = info
|
288
|
+
if descr
|
289
|
+
return false unless descr[:user]
|
290
|
+
return false unless descr[:usage]
|
291
|
+
@user = descr[:user]
|
292
|
+
true
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
######################################################
|
297
|
+
# User login/password
|
298
|
+
######################################################
|
299
|
+
|
300
|
+
# login and return an auth_token
|
301
|
+
# Auth token can be retained and used in creating
|
302
|
+
# new clients, avoiding login.
|
303
|
+
def login(user, password)
|
304
|
+
status, body, headers = json_post(path(VMC::USERS_PATH, user, "tokens"), {:password => password})
|
305
|
+
response_info = json_parse(body)
|
306
|
+
if response_info
|
307
|
+
@user = user
|
308
|
+
@auth_token = response_info[:token]
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# sets the password for the current logged user
|
313
|
+
def change_password(new_password)
|
314
|
+
check_login_status
|
315
|
+
user_info = json_get(path(VMC::USERS_PATH, @user))
|
316
|
+
if user_info
|
317
|
+
user_info[:password] = new_password
|
318
|
+
json_put(path(VMC::USERS_PATH, @user), user_info)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
######################################################
|
323
|
+
# System administration
|
324
|
+
######################################################
|
325
|
+
|
326
|
+
def proxy=(proxy)
|
327
|
+
@proxy = proxy
|
328
|
+
end
|
329
|
+
|
330
|
+
def proxy_for(proxy)
|
331
|
+
@proxy = proxy
|
332
|
+
end
|
333
|
+
|
334
|
+
def users
|
335
|
+
check_login_status
|
336
|
+
json_get(VMC::USERS_PATH)
|
337
|
+
end
|
338
|
+
|
339
|
+
def add_user(user_email, password)
|
340
|
+
json_post(VMC::USERS_PATH, { :email => user_email, :password => password })
|
341
|
+
end
|
342
|
+
|
343
|
+
def delete_user(user_email)
|
344
|
+
check_login_status
|
345
|
+
http_delete(path(VMC::USERS_PATH, user_email))
|
346
|
+
end
|
347
|
+
|
348
|
+
######################################################
|
349
|
+
# Uhuru extension
|
350
|
+
######################################################
|
351
|
+
|
352
|
+
def via_uhuru_cloud=(via_uhuru_cloud)
|
353
|
+
@via_uhuru_cloud = via_uhuru_cloud
|
354
|
+
end
|
355
|
+
|
356
|
+
def auth_token=(auth_token)
|
357
|
+
@auth_token = auth_token
|
358
|
+
end
|
359
|
+
|
360
|
+
def cloud_team=(cloud_team)
|
361
|
+
@cloud_team = cloud_team
|
362
|
+
end
|
363
|
+
|
364
|
+
def proxy_realm=(proxy_realm)
|
365
|
+
@proxy_realm = proxy_realm
|
366
|
+
end
|
367
|
+
|
368
|
+
def get_user_cloud_teams
|
369
|
+
cloud_ids = json_get("../usercloudteams/")
|
370
|
+
end
|
371
|
+
|
372
|
+
def get_cloud_team(id)
|
373
|
+
json_get("../cloud_teams/#{id}")
|
374
|
+
end
|
375
|
+
|
376
|
+
def get_detailed_cloud_teams
|
377
|
+
ret = []
|
378
|
+
get_user_cloud_teams.each { |cloud_team_id|
|
379
|
+
ret << get_cloud_team(cloud_team_id)
|
380
|
+
}
|
381
|
+
ret
|
382
|
+
end
|
383
|
+
|
384
|
+
def get_cloud_entity(cloud_id)
|
385
|
+
@cloud_entity = json_get("../clouds/#{cloud_id}")
|
386
|
+
end
|
387
|
+
|
388
|
+
def get_cloud_domain
|
389
|
+
ct = get_cloud_team(@cloud_team)
|
390
|
+
cloud_id = (get_cloud_team(@cloud_team)[:Cloud] || get_cloud_team(@cloud_team)[:CloudTeamCloud])[:Id]
|
391
|
+
json_get("../clouds/#{cloud_id}")[:Domain]
|
392
|
+
end
|
393
|
+
|
394
|
+
def uhuru_version
|
395
|
+
if @via_uhuru_cloud
|
396
|
+
status, body, headers = http_get("../version/")
|
397
|
+
else
|
398
|
+
status, body, headers = http_get("version/")
|
399
|
+
end
|
400
|
+
body
|
401
|
+
end
|
402
|
+
|
403
|
+
def raw_uhuru_version
|
404
|
+
if @via_uhuru_cloud
|
405
|
+
http_get("../version")
|
406
|
+
else
|
407
|
+
http_get("version")
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
# login to Uhuru App Cloud with a one time token or a regular authentication token
|
412
|
+
def uhuru_login(token)
|
413
|
+
@via_uhuru_cloud = true;
|
414
|
+
# check for one time token
|
415
|
+
if token =~ /\A(\w+-)+\w+\z/
|
416
|
+
response_target = json_get("../one_time_tokens/#{token}")
|
417
|
+
if response_target
|
418
|
+
@auth_token = response_target[:token]
|
419
|
+
@cloud_team = response_target[:cloud_team]
|
420
|
+
@proxy_realm = response_target[:realm]
|
421
|
+
response_target[:cloud_domain] = get_cloud_domain if @cloud_team
|
422
|
+
response_target
|
423
|
+
end
|
424
|
+
else
|
425
|
+
@auth_token = token
|
426
|
+
body = json_get("../usercloudteams/")
|
427
|
+
if body
|
428
|
+
{:token => token}
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
end
|
433
|
+
|
434
|
+
######################################################
|
435
|
+
|
436
|
+
def self.path(*path)
|
437
|
+
path.flatten.collect { |x|
|
438
|
+
URI.encode x.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
|
439
|
+
}.join("/")
|
440
|
+
end
|
441
|
+
|
442
|
+
private
|
443
|
+
|
444
|
+
def path(*args, &blk)
|
445
|
+
self.class.path(*args, &blk)
|
446
|
+
end
|
447
|
+
|
448
|
+
def json_get(url)
|
449
|
+
status, body, headers = http_get(url, 'application/json')
|
450
|
+
json_parse(body)
|
451
|
+
rescue JSON::ParserError
|
452
|
+
raise BadResponse, "Can't parse response into JSON", body
|
453
|
+
end
|
454
|
+
|
455
|
+
def json_post(url, payload)
|
456
|
+
http_post(url, payload.to_json, 'application/json')
|
457
|
+
end
|
458
|
+
|
459
|
+
def json_put(url, payload)
|
460
|
+
http_put(url, payload.to_json, 'application/json')
|
461
|
+
end
|
462
|
+
|
463
|
+
def json_parse(str)
|
464
|
+
if str
|
465
|
+
JSON.parse(str, :symbolize_names => true)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
require 'rest_client'
|
470
|
+
|
471
|
+
# HTTP helpers
|
472
|
+
|
473
|
+
def http_get(path, content_type=nil)
|
474
|
+
request(:get, path, content_type)
|
475
|
+
end
|
476
|
+
|
477
|
+
def http_post(path, body, content_type=nil)
|
478
|
+
request(:post, path, content_type, body)
|
479
|
+
end
|
480
|
+
|
481
|
+
def http_put(path, body, content_type=nil)
|
482
|
+
request(:put, path, content_type, body)
|
483
|
+
end
|
484
|
+
|
485
|
+
def http_delete(path)
|
486
|
+
request(:delete, path)
|
487
|
+
end
|
488
|
+
|
489
|
+
def request(method, path, content_type = nil, payload = nil, headers = {})
|
490
|
+
headers = headers.dup
|
491
|
+
headers['AUTHORIZATION'] = @auth_token if @auth_token
|
492
|
+
headers['PROXY-USER'] = @proxy if @proxy
|
493
|
+
headers['CloudTeam'] = @cloud_team if @cloud_team
|
494
|
+
headers['ProxyRealm'] = @proxy_realm if @proxy_realm
|
495
|
+
path = "cf/" + path if @via_uhuru_cloud
|
496
|
+
|
497
|
+
if content_type
|
498
|
+
headers['Content-Type'] = content_type
|
499
|
+
headers['Accept'] = content_type
|
500
|
+
end
|
501
|
+
|
502
|
+
req = {
|
503
|
+
:method => method, :url => "#{@target}/#{path}",
|
504
|
+
:payload => payload, :headers => headers, :multipart => true
|
505
|
+
}
|
506
|
+
status, body, response_headers = perform_http_request(req)
|
507
|
+
|
508
|
+
if request_failed?(status)
|
509
|
+
# FIXME, old cc returned 400 on not found for file access
|
510
|
+
err = (status == 404 || status == 400) ? NotFound : TargetError
|
511
|
+
raise err, parse_error_message(status, body)
|
512
|
+
else
|
513
|
+
return status, body, response_headers
|
514
|
+
end
|
515
|
+
rescue URI::Error, SocketError, Errno::ECONNREFUSED => e
|
516
|
+
raise BadTarget, "Cannot access target (%s)" % [ e.message ]
|
517
|
+
end
|
518
|
+
|
519
|
+
def request_failed?(status)
|
520
|
+
VMC_HTTP_ERROR_CODES.detect{|error_code| status >= error_code}
|
521
|
+
end
|
522
|
+
|
523
|
+
def perform_http_request(req)
|
524
|
+
proxy_uri = URI.parse(req[:url]).find_proxy()
|
525
|
+
RestClient.proxy = proxy_uri.to_s if proxy_uri
|
526
|
+
|
527
|
+
# Setup tracing if needed
|
528
|
+
unless trace.nil?
|
529
|
+
req[:headers]['X-VCAP-Trace'] = (trace == true ? '22' : trace)
|
530
|
+
end
|
531
|
+
|
532
|
+
result = nil
|
533
|
+
RestClient::Request.execute(req) do |response, request|
|
534
|
+
result = [ response.code, response.body, response.headers ]
|
535
|
+
unless trace.nil?
|
536
|
+
puts '>>>'
|
537
|
+
puts "PROXY: #{RestClient.proxy}" if RestClient.proxy
|
538
|
+
puts "REQUEST: #{req[:method]} #{req[:url]}"
|
539
|
+
puts "RESPONSE_HEADERS:"
|
540
|
+
response.headers.each do |key, value|
|
541
|
+
puts " #{key} : #{value}"
|
542
|
+
end
|
543
|
+
puts "REQUEST_BODY: #{req[:payload]}" if req[:payload]
|
544
|
+
puts "RESPONSE: [#{response.code}]"
|
545
|
+
begin
|
546
|
+
puts JSON.pretty_generate(JSON.parse(response.body))
|
547
|
+
rescue
|
548
|
+
puts "#{response.body}"
|
549
|
+
end
|
550
|
+
puts '<<<'
|
551
|
+
end
|
552
|
+
end
|
553
|
+
result
|
554
|
+
rescue Net::HTTPBadResponse => e
|
555
|
+
raise BadTarget "Received bad HTTP response from target: #{e}"
|
556
|
+
rescue SystemCallError, RestClient::Exception => e
|
557
|
+
raise HTTPException, "HTTP exception: #{e.class}:#{e}"
|
558
|
+
end
|
559
|
+
|
560
|
+
def truncate(str, limit = 30)
|
561
|
+
etc = '...'
|
562
|
+
stripped = str.strip[0..limit]
|
563
|
+
if stripped.length > limit
|
564
|
+
stripped + etc
|
565
|
+
else
|
566
|
+
stripped
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
def parse_error_message(status, body)
|
571
|
+
parsed_body = json_parse(body.to_s)
|
572
|
+
if parsed_body && parsed_body[:code] && parsed_body[:description]
|
573
|
+
desc = parsed_body[:description].gsub("\"","'")
|
574
|
+
"Error #{parsed_body[:code]}: #{desc}"
|
575
|
+
else
|
576
|
+
"Error (HTTP #{status}): #{body}"
|
577
|
+
end
|
578
|
+
rescue JSON::ParserError
|
579
|
+
if body.nil? || body.empty?
|
580
|
+
"Error (#{status}): No Response Received"
|
581
|
+
else
|
582
|
+
body_out = trace ? body : truncate(body)
|
583
|
+
"Error (JSON #{status}): #{body_out}"
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
def check_login_status
|
588
|
+
raise AuthError unless @user || logged_in?
|
589
|
+
end
|
590
|
+
|
591
|
+
end
|