af 0.3.12

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.
Files changed (45) hide show
  1. data/LICENSE +24 -0
  2. data/README.md +92 -0
  3. data/Rakefile +17 -0
  4. data/bin/af +6 -0
  5. data/lib/cli.rb +30 -0
  6. data/lib/cli/commands/admin.rb +77 -0
  7. data/lib/cli/commands/apps.rb +940 -0
  8. data/lib/cli/commands/base.rb +79 -0
  9. data/lib/cli/commands/misc.rb +128 -0
  10. data/lib/cli/commands/services.rb +86 -0
  11. data/lib/cli/commands/user.rb +60 -0
  12. data/lib/cli/config.rb +110 -0
  13. data/lib/cli/core_ext.rb +119 -0
  14. data/lib/cli/errors.rb +19 -0
  15. data/lib/cli/frameworks.rb +109 -0
  16. data/lib/cli/runner.rb +490 -0
  17. data/lib/cli/services_helper.rb +78 -0
  18. data/lib/cli/usage.rb +104 -0
  19. data/lib/cli/version.rb +7 -0
  20. data/lib/cli/zip_util.rb +77 -0
  21. data/lib/vmc.rb +3 -0
  22. data/lib/vmc/client.rb +451 -0
  23. data/lib/vmc/const.rb +21 -0
  24. data/spec/assets/app_info.txt +9 -0
  25. data/spec/assets/app_listings.txt +9 -0
  26. data/spec/assets/bad_create_app.txt +9 -0
  27. data/spec/assets/delete_app.txt +9 -0
  28. data/spec/assets/global_service_listings.txt +9 -0
  29. data/spec/assets/good_create_app.txt +9 -0
  30. data/spec/assets/good_create_service.txt +9 -0
  31. data/spec/assets/info_authenticated.txt +27 -0
  32. data/spec/assets/info_return.txt +15 -0
  33. data/spec/assets/info_return_bad.txt +16 -0
  34. data/spec/assets/list_users.txt +13 -0
  35. data/spec/assets/login_fail.txt +9 -0
  36. data/spec/assets/login_success.txt +9 -0
  37. data/spec/assets/sample_token.txt +1 -0
  38. data/spec/assets/service_already_exists.txt +9 -0
  39. data/spec/assets/service_listings.txt +9 -0
  40. data/spec/assets/service_not_found.txt +9 -0
  41. data/spec/assets/user_info.txt +9 -0
  42. data/spec/spec_helper.rb +11 -0
  43. data/spec/unit/cli_opts_spec.rb +68 -0
  44. data/spec/unit/client_spec.rb +332 -0
  45. metadata +221 -0
@@ -0,0 +1,78 @@
1
+
2
+ module VMC::Cli
3
+ module ServicesHelper
4
+ def display_system_services(services=nil)
5
+ services ||= client.services_info
6
+
7
+ display "\n============== System Services ==============\n\n"
8
+
9
+ return display "No system services available" if services.empty?
10
+
11
+ displayed_services = []
12
+ services.each do |service_type, value|
13
+ value.each do |vendor, version|
14
+ version.each do |version_str, service|
15
+ displayed_services << [ vendor, version_str, service[:description] ]
16
+ end
17
+ end
18
+ end
19
+ displayed_services.sort! { |a, b| a.first.to_s <=> b.first.to_s}
20
+
21
+ services_table = table do |t|
22
+ t.headings = 'Service', 'Version', 'Description'
23
+ displayed_services.each { |s| t << s }
24
+ end
25
+ display services_table
26
+ end
27
+
28
+ def display_provisioned_services(services=nil)
29
+ services ||= client.services
30
+ display "\n=========== Provisioned Services ============\n\n"
31
+ display_provisioned_services_table(services)
32
+ end
33
+
34
+ def display_provisioned_services_table(services)
35
+ return unless services && !services.empty?
36
+ services_table = table do |t|
37
+ t.headings = 'Name', 'Service'
38
+ services.each do |service|
39
+ t << [ service[:name], service[:vendor] ]
40
+ end
41
+ end
42
+ display services_table
43
+ end
44
+
45
+ def create_service_banner(service, name, display_name=false)
46
+ sn = " [#{name}]" if display_name
47
+ display "Creating Service#{sn}: ", false
48
+ client.create_service(service, name)
49
+ display 'OK'.green
50
+ end
51
+
52
+ def bind_service_banner(service, appname, check_restart=true)
53
+ display "Binding Service: ", false
54
+ client.bind_service(service, appname)
55
+ display 'OK'.green
56
+ check_app_for_restart(appname) if check_restart
57
+ end
58
+
59
+ def unbind_service_banner(service, appname, check_restart=true)
60
+ display "Unbinding Service: ", false
61
+ client.unbind_service(service, appname)
62
+ display 'OK'.green
63
+ check_app_for_restart(appname) if check_restart
64
+ end
65
+
66
+ def random_service_name(service)
67
+ r = "%04x" % [rand(0x0100000)]
68
+ "#{service.to_s}-#{r}"
69
+ end
70
+
71
+ def check_app_for_restart(appname)
72
+ app = client.app_info(appname)
73
+ cmd = VMC::Cli::Command::Apps.new(@options)
74
+ cmd.restart(appname) if app[:state] == 'STARTED'
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,104 @@
1
+ class VMC::Cli::Runner
2
+
3
+ def basic_usage
4
+ "Usage: af [options] command [<args>] [command_options]\n" +
5
+ "Try 'af help [command]' or 'af 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 af commands are:
26
+
27
+ Getting Started
28
+ target [url] Reports current target or sets a new target
29
+ login [email] [--email, --passwd] Login
30
+ info System and account information
31
+
32
+ Applications
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] --no-start Do not auto-start the application
43
+
44
+ Application Operations
45
+ start <appname> Start the application
46
+ stop <appname> Stop the application
47
+ restart <appname> Restart the application
48
+ delete <appname> Delete the application
49
+ rename <appname> <newname> Rename the application
50
+
51
+ Application Updates
52
+ update <appname> [--path] 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
+ Services
72
+ services Lists of services available and provisioned
73
+ create-service <service> [--name,--bind] Create a provisioned service
74
+ create-service <service> <name> Create a provisioned service and assign it <name>
75
+ create-service <service> <name> <app> Create a provisioned service and assign it <name>, and bind to <app>
76
+ delete-service [servicename] Delete a provisioned service
77
+ bind-service <servicename> <appname> Bind a service to an application
78
+ unbind-service <servicename> <appname> Unbind service from the application
79
+ clone-services <src-app> <dest-app> Clone service bindings from <src-app> application to <dest-app>
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.12'
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
@@ -0,0 +1,3 @@
1
+ module VMC; end
2
+
3
+ require 'vmc/client'
@@ -0,0 +1,451 @@
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, 403, 404, 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(VMC::GLOBAL_SERVICES_PATH)
64
+ end
65
+
66
+ ######################################################
67
+ # Apps
68
+ ######################################################
69
+
70
+ def apps
71
+ check_login_status
72
+ json_get(VMC::APPS_PATH)
73
+ end
74
+
75
+ def create_app(name, manifest={})
76
+ check_login_status
77
+ app = manifest.dup
78
+ app[:name] = name
79
+ app[:instances] ||= 1
80
+ json_post(VMC::APPS_PATH, app)
81
+ end
82
+
83
+ def update_app(name, manifest)
84
+ check_login_status
85
+ json_put("#{VMC::APPS_PATH}/#{name}", manifest)
86
+ end
87
+
88
+ def upload_app(name, zipfile, resource_manifest=nil)
89
+ #FIXME, manifest should be allowed to be null, here for compatability with old cc's
90
+ resource_manifest ||= []
91
+ check_login_status
92
+ upload_data = {:_method => 'put'}
93
+ if zipfile
94
+ if zipfile.is_a? File
95
+ file = zipfile
96
+ else
97
+ file = File.new(zipfile, 'rb')
98
+ end
99
+ upload_data[:application] = file
100
+ end
101
+ upload_data[:resources] = resource_manifest.to_json if resource_manifest
102
+ http_post("#{VMC::APPS_PATH}/#{name}/application", upload_data)
103
+ end
104
+
105
+ def delete_app(name)
106
+ check_login_status
107
+ http_delete("#{VMC::APPS_PATH}/#{name}")
108
+ end
109
+
110
+ def app_info(name)
111
+ check_login_status
112
+ json_get("#{VMC::APPS_PATH}/#{name}")
113
+ end
114
+
115
+ def app_update_info(name)
116
+ check_login_status
117
+ json_get("#{VMC::APPS_PATH}/#{name}/update")
118
+ end
119
+
120
+ def app_stats(name)
121
+ check_login_status
122
+ stats_raw = json_get("#{VMC::APPS_PATH}/#{name}/stats")
123
+ stats = []
124
+ stats_raw.each_pair do |k, entry|
125
+ # Skip entries with no stats
126
+ next unless entry[:stats]
127
+ entry[:instance] = k.to_s.to_i
128
+ entry[:state] = entry[:state].to_sym if entry[:state]
129
+ stats << entry
130
+ end
131
+ stats.sort { |a,b| a[:instance] - b[:instance] }
132
+ end
133
+
134
+ def app_instances(name)
135
+ check_login_status
136
+ json_get("#{VMC::APPS_PATH}/#{name}/instances")
137
+ end
138
+
139
+ def app_crashes(name)
140
+ check_login_status
141
+ json_get("#{VMC::APPS_PATH}/#{name}/crashes")
142
+ end
143
+
144
+ # List the directory or download the actual file indicated by
145
+ # the path.
146
+ def app_files(name, path, instance=0)
147
+ check_login_status
148
+ url = "#{VMC::APPS_PATH}/#{name}/instances/#{instance}/files/#{path}"
149
+ url.gsub!('//', '/')
150
+ _, body, headers = http_get(url)
151
+ body
152
+ end
153
+
154
+ ######################################################
155
+ # Services
156
+ ######################################################
157
+
158
+ # listing of services that are available in the system
159
+ def services
160
+ check_login_status
161
+ json_get(VMC::SERVICES_PATH)
162
+ end
163
+
164
+ def create_service(service, name)
165
+ check_login_status
166
+ services = services_info
167
+ services ||= []
168
+ service_hash = nil
169
+
170
+ service = service.to_s
171
+
172
+ # FIXME!
173
+ services.each do |service_type, value|
174
+ value.each do |vendor, version|
175
+ version.each do |version_str, service_descr|
176
+ if service == service_descr[:vendor]
177
+ service_hash = {
178
+ :type => service_descr[:type], :tier => 'free',
179
+ :vendor => service, :version => version_str
180
+ }
181
+ break
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ raise TargetError, "Service [#{service}] is not a valid service choice" unless service_hash
188
+ service_hash[:name] = name
189
+ json_post(VMC::SERVICES_PATH, service_hash)
190
+ end
191
+
192
+ def delete_service(name)
193
+ check_login_status
194
+ svcs = services || []
195
+ names = svcs.collect { |s| s[:name] }
196
+ raise TargetError, "Service [#{name}] not a valid service" unless names.include? name
197
+ http_delete("#{VMC::SERVICES_PATH}/#{name}")
198
+ end
199
+
200
+ def bind_service(service, appname)
201
+ check_login_status
202
+ app = app_info(appname)
203
+ services = app[:services] || []
204
+ app[:services] = services << service
205
+ update_app(appname, app)
206
+ end
207
+
208
+ def unbind_service(service, appname)
209
+ check_login_status
210
+ app = app_info(appname)
211
+ services = app[:services] || []
212
+ services.delete(service)
213
+ app[:services] = services
214
+ update_app(appname, app)
215
+ end
216
+
217
+ ######################################################
218
+ # Resources
219
+ ######################################################
220
+
221
+ # Send in a resources manifest array to the system to have
222
+ # it check what is needed to actually send. Returns array
223
+ # indicating what is needed. This returned manifest should be
224
+ # sent in with the upload if resources were removed.
225
+ # E.g. [{:sha1 => xxx, :size => xxx, :fn => filename}]
226
+ def check_resources(resources)
227
+ check_login_status
228
+ status, body, headers = json_post(VMC::RESOURCES_PATH, resources)
229
+ json_parse(body)
230
+ end
231
+
232
+ ######################################################
233
+ # Validation Helpers
234
+ ######################################################
235
+
236
+ # Checks that the target is valid
237
+ def target_valid?
238
+ return false unless descr = info
239
+ return false unless descr[:name]
240
+ return false unless descr[:build]
241
+ return false unless descr[:version]
242
+ return false unless descr[:support]
243
+ true
244
+ rescue
245
+ false
246
+ end
247
+
248
+ # Checks that the auth_token is valid
249
+ def logged_in?
250
+ descr = info
251
+ if descr
252
+ return false unless descr[:user]
253
+ return false unless descr[:usage]
254
+ @user = descr[:user]
255
+ true
256
+ end
257
+ end
258
+
259
+ ######################################################
260
+ # User login/password
261
+ ######################################################
262
+
263
+ # login and return an auth_token
264
+ # Auth token can be retained and used in creating
265
+ # new clients, avoiding login.
266
+ def login(user, password)
267
+ status, body, headers = json_post("#{VMC::USERS_PATH}/#{user}/tokens", {:password => password})
268
+ response_info = json_parse(body)
269
+ if response_info
270
+ @user = user
271
+ @auth_token = response_info[:token]
272
+ end
273
+ end
274
+
275
+ # sets the password for the current logged user
276
+ def change_password(new_password)
277
+ check_login_status
278
+ user_info = json_get("#{VMC::USERS_PATH}/#{@user}")
279
+ if user_info
280
+ user_info[:password] = new_password
281
+ json_put("#{VMC::USERS_PATH}/#{@user}", user_info)
282
+ end
283
+ end
284
+
285
+ ######################################################
286
+ # System administration
287
+ ######################################################
288
+
289
+ def proxy=(proxy)
290
+ @proxy = proxy
291
+ end
292
+
293
+ def proxy_for(proxy)
294
+ @proxy = proxy
295
+ end
296
+
297
+ def users
298
+ check_login_status
299
+ json_get(VMC::USERS_PATH)
300
+ end
301
+
302
+ def add_user(user_email, password)
303
+ json_post(VMC::USERS_PATH, { :email => user_email, :password => password })
304
+ end
305
+
306
+ def delete_user(user_email)
307
+ check_login_status
308
+ http_delete("#{VMC::USERS_PATH}/#{user_email}")
309
+ end
310
+
311
+ ######################################################
312
+
313
+ private
314
+
315
+ def json_get(url)
316
+ status, body, headers = http_get(url, 'application/json')
317
+ json_parse(body)
318
+ rescue JSON::ParserError
319
+ raise BadResponse, "Can't parse response into JSON", body
320
+ end
321
+
322
+ def json_post(url, payload)
323
+ http_post(url, payload.to_json, 'application/json')
324
+ end
325
+
326
+ def json_put(url, payload)
327
+ http_put(url, payload.to_json, 'application/json')
328
+ end
329
+
330
+ def json_parse(str)
331
+ if str
332
+ JSON.parse(str, :symbolize_names => true)
333
+ end
334
+ end
335
+
336
+ require 'rest_client'
337
+
338
+ # HTTP helpers
339
+
340
+ def http_get(path, content_type=nil)
341
+ request(:get, path, content_type)
342
+ end
343
+
344
+ def http_post(path, body, content_type=nil)
345
+ request(:post, path, content_type, body)
346
+ end
347
+
348
+ def http_put(path, body, content_type=nil)
349
+ request(:put, path, content_type, body)
350
+ end
351
+
352
+ def http_delete(path)
353
+ request(:delete, path)
354
+ end
355
+
356
+ def request(method, path, content_type = nil, payload = nil, headers = {})
357
+ headers = headers.dup
358
+ headers['AUTHORIZATION'] = @auth_token if @auth_token
359
+ headers['PROXY-USER'] = @proxy if @proxy
360
+
361
+ if content_type
362
+ headers['Content-Type'] = content_type
363
+ headers['Accept'] = content_type
364
+ end
365
+
366
+ req = {
367
+ :method => method, :url => "#{@target}#{path}",
368
+ :payload => payload, :headers => headers
369
+ }
370
+ status, body, response_headers = perform_http_request(req)
371
+
372
+ if VMC_HTTP_ERROR_CODES.include?(status)
373
+ # FIXME, old cc returned 400 on not found for file access
374
+ err = (status == 404 || status == 400) ? NotFound : TargetError
375
+ raise err, parse_error_message(status, body)
376
+ else
377
+ return status, body, response_headers
378
+ end
379
+ rescue URI::Error, SocketError, Errno::ECONNREFUSED => e
380
+ raise BadTarget, "Cannot access target (%s)" % [ e.message ]
381
+ end
382
+
383
+ def perform_http_request(req)
384
+ proxy_uri = URI.parse(req[:url]).find_proxy()
385
+ RestClient.proxy = proxy_uri.to_s if proxy_uri
386
+
387
+ # Setup tracing if needed
388
+ unless trace.nil?
389
+ req[:headers]['X-VCAP-Trace'] = (trace == true ? '22' : trace)
390
+ end
391
+
392
+ result = nil
393
+ RestClient::Request.execute(req) do |response, request|
394
+ result = [ response.code, response.body, response.headers ]
395
+ unless trace.nil?
396
+ puts '>>>'
397
+ puts "PROXY: #{RestClient.proxy}" if RestClient.proxy
398
+ puts "REQUEST: #{req[:method]} #{req[:url]}"
399
+ puts "RESPONSE_HEADERS:"
400
+ response.headers.each do |key, value|
401
+ puts " #{key} : #{value}"
402
+ end
403
+ puts "REQUEST_BODY: #{req[:payload]}" if req[:payload]
404
+ puts "RESPONSE: [#{response.code}]"
405
+ begin
406
+ puts JSON.pretty_generate(JSON.parse(response.body))
407
+ rescue
408
+ puts "#{response.body}"
409
+ end
410
+ puts '<<<'
411
+ end
412
+ end
413
+ result
414
+ rescue Net::HTTPBadResponse => e
415
+ raise BadTarget "Received bad HTTP response from target: #{e}"
416
+ rescue SystemCallError, RestClient::Exception => e
417
+ raise HTTPException, "HTTP exception: #{e.class}:#{e}"
418
+ end
419
+
420
+ def truncate(str, limit = 30)
421
+ etc = '...'
422
+ stripped = str.strip[0..limit]
423
+ if stripped.length > limit
424
+ stripped + etc
425
+ else
426
+ stripped
427
+ end
428
+ end
429
+
430
+ def parse_error_message(status, body)
431
+ parsed_body = json_parse(body.to_s)
432
+ if parsed_body && parsed_body[:code] && parsed_body[:description]
433
+ desc = parsed_body[:description].gsub("\"","'")
434
+ "Error #{parsed_body[:code]}: #{desc}"
435
+ else
436
+ "Error (HTTP #{status}): #{body}"
437
+ end
438
+ rescue JSON::ParserError
439
+ if body.nil? || body.empty?
440
+ "Error (#{status}): No Response Received"
441
+ else
442
+ body_out = trace ? body : truncate(body)
443
+ "Error (JSON #{status}): #{body_out}"
444
+ end
445
+ end
446
+
447
+ def check_login_status
448
+ raise AuthError unless @user || logged_in?
449
+ end
450
+
451
+ end