af 0.3.12

Sign up to get free protection for your applications and to get access to all the features.
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