vmc_virgo 0.0.1.beta

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