vmc_virgo 0.0.1.beta

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/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: []