ravello-sdk 0.9

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 (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ravello-sdk.rb +427 -0
  3. metadata +44 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8e22bc14b505f0312ca1ec12dab8dc3ccaaebfce
4
+ data.tar.gz: 4f0a9072a6547b515dc3afb55e1c2e7b19b02cf8
5
+ SHA512:
6
+ metadata.gz: e4f35dbecf51404a26d46851e67ec46a417940aeca29d6143ea6d2dc4900b74f89959c460e3d80bd5531f761603c58cbd4128fa56bc9581543d14dae62413b6d
7
+ data.tar.gz: 55c6a7da642fe7d0f70136e28285e82d55cb7eea6073563ef43a6fce74ef4ad60b661c5b3d8156e57c8a2f75ac0d3e6448738ecf16226ea7dbf599d12b887573
@@ -0,0 +1,427 @@
1
+ require "uri"
2
+ require "json"
3
+ require "logger"
4
+ require "base64"
5
+ require "net/http"
6
+
7
+
8
+ class Net::HTTPResponse
9
+
10
+ def entity
11
+ @entity if defined? @entity
12
+ end
13
+
14
+ def entity=(value)
15
+ @entity = value
16
+ end
17
+
18
+ end
19
+
20
+ module Ravello
21
+
22
+ DEFAULT_URL = "https://cloud.ravellosystems.com/services"
23
+
24
+ @@prng = Random.new
25
+
26
+ def deep_copy(o)
27
+ Marshal.load(Marshal.dump(o))
28
+ end
29
+
30
+ def random_luid
31
+ @@prng.rand 1<<63
32
+ end
33
+
34
+ def update_luids!(o)
35
+ if o.is_a? Hash
36
+ o.each_pair do |key,value|
37
+ o[key] = if key == 'id' then random_luid else update_luids! value end
38
+ end
39
+ elsif o.is_a? Array
40
+ o.map! do |x| update_luids! x end
41
+ end
42
+ o
43
+ end
44
+
45
+ def update_luids(o)
46
+ update_luids!(deep_copy o)
47
+ end
48
+
49
+ def application_state(app)
50
+ if !app.is_a? Hash
51
+ raise ArgumentError, "expecting a Hash, got #{app}"
52
+ elsif !app['deployment'].is_a? Hash
53
+ return nil
54
+ elsif !app['deployment']['vms'].is_a? Array
55
+ return nil
56
+ end
57
+ states = app['deployment']['vms'].map { |vm| vm['state'] } .uniq
58
+ if states.length == 1 then states[0] else states end
59
+ end
60
+
61
+ module_function :deep_copy, :random_luid, :update_luids, :update_luids!
62
+
63
+ class Client
64
+
65
+ attr_accessor :retries, :timeout
66
+ attr_reader :url, :user_info
67
+
68
+ def initialize(options={})
69
+ @username = options[:username]
70
+ @password = options[:password]
71
+ @retries = options.fetch(:retries, 3)
72
+ @timeout = options.fetch(:timeout, 60)
73
+ @logger = Logger.new(STDERR)
74
+ @logger.level = if $DEBUG then Logger::DEBUG \
75
+ elsif $VERBOSE then Logger::INFO else Logger::WARN end
76
+ @redirects = 3
77
+ @autologin = true
78
+ @cookies = nil
79
+ @user_info = nil
80
+ @connection = nil
81
+ set_url(options.fetch(:url, DEFAULT_URL))
82
+ end
83
+
84
+ def connected?
85
+ !@connection.nil?
86
+ end
87
+
88
+ def closed?
89
+ @connection.nil?
90
+ end
91
+
92
+ def logged_in?
93
+ !@cookies.nil?
94
+ end
95
+
96
+ def url=(url)
97
+ set_url(url)
98
+ end
99
+
100
+ def connect(url=nil)
101
+ url = url unless url.nil?
102
+ do_connect
103
+ end
104
+
105
+ def login(username=nil, password=nil)
106
+ raise "already logged in" if logged_in?
107
+ @username = username unless username.nil?
108
+ @password = password unless password.nil?
109
+ raise "username and password must be set" unless have_credentials?
110
+ do_login
111
+ end
112
+
113
+ def logout
114
+ do_logout if logged_in?
115
+ end
116
+
117
+ def close
118
+ @connection.finish if !@connection.nil?
119
+ @connection = nil
120
+ @cookies = nil
121
+ end
122
+
123
+ def request(method, path, entity=nil)
124
+ body = JSON.generate(entity) unless entity.nil?
125
+ response = make_request(method, path, body)
126
+ response.entity
127
+ end
128
+
129
+ def wait_for(obj, timeout, &block)
130
+ end_time = Time.now + timeout
131
+ raise 'object requires a "href" key' if !obj.key? 'href'
132
+ href = obj['href']
133
+ while end_time >= Time.now do
134
+ break if yield request(:GET, href)
135
+ sleep 5
136
+ end
137
+ raise 'timeout' if end_time < Time.now
138
+ end
139
+
140
+ def get_application(id)
141
+ if id.is_a? Hash then id = id['id'] end
142
+ request(:GET, "/applications/#{id}")
143
+ end
144
+
145
+ def get_applications(filter={})
146
+ result = request(:GET, '/applications')
147
+ result.select! do |app| match_filter(app, filter) end if !filter.empty?
148
+ result
149
+ end
150
+
151
+ def create_application(app)
152
+ request(:POST, '/applications', app)
153
+ end
154
+
155
+ def update_application(app)
156
+ request(:PUT, "/applications/#{app['id']}", app)
157
+ end
158
+
159
+ def delete_application(id)
160
+ if id.is_a? Hash then id = id['id'] end
161
+ request(:DELETE, "/applications/#{id}")
162
+ end
163
+
164
+ def publish_application(id, req={})
165
+ if id.is_a? Hash then id = id['id'] end
166
+ request(:POST, "/applications/#{id}/publish", req)
167
+ end
168
+
169
+ def start_application(id)
170
+ if id.is_a? Hash then id = id['id'] end
171
+ request(:POST, "/applications/#{id}/start")
172
+ end
173
+
174
+ def stop_application(id)
175
+ if id.is_a? Hash then id = id['id'] end
176
+ request(:POST, "/applications/#{id}/stop")
177
+ end
178
+
179
+ def restart_application(id)
180
+ if id.is_a? Hash then id = id['id'] end
181
+ request(:POST, "/applications/#{id}/restart")
182
+ end
183
+
184
+ def publish_application_updates(id)
185
+ if id.is_a? Hash then id = id['id'] end
186
+ request(:POST, "/applications/#{id}/publishUpdates")
187
+ end
188
+
189
+ def set_application_expiration(id, req)
190
+ if id.is_a? Hash then id = id['id'] end
191
+ request(:POST, "/applications/#{id}/setExpiration", req)
192
+ end
193
+
194
+ def start_vm(appid, vmid)
195
+ if appid.is_a? Hash then appid = appid['id'] end
196
+ if vmid.is_a? Hash then vmid = vmid['id'] end
197
+ request(:POST, "/applications/#{appid}/vms/#{vmid}/start")
198
+ end
199
+
200
+ def stop_vm(appid, vmid)
201
+ if appid.is_a? Hash then appid = appid['id'] end
202
+ if vmid.is_a? Hash then vmid = vmid['id'] end
203
+ request(:POST, "/applications/#{appid}/vms/#{vmid}/stop")
204
+ end
205
+
206
+ def restart_vm(appid, vmid)
207
+ if appid.is_a? Hash then appid = appid['id'] end
208
+ if vmid.is_a? Hash then vmid = vmid['id'] end
209
+ request(:POST, "/applications/#{appid}/vms/#{vmid}/restart")
210
+ end
211
+
212
+ def get_blueprint(id)
213
+ if id.is_a? Hash then id = id['id'] end
214
+ request(:GET, "/blueprints/#{id}")
215
+ end
216
+
217
+ def get_blueprints(filter={})
218
+ result = request(:GET, '/blueprints')
219
+ result.select! do |bp| match_filter(bp, filter) end if !filter.empty?
220
+ result
221
+ end
222
+
223
+ def create_blueprint(req)
224
+ request(:POST, '/blueprints', req)
225
+ end
226
+
227
+ def delete_blueprint(id)
228
+ if id.is_a? Hash then id = id['id'] end
229
+ request(:DELETE, "/blueprints/#{id}")
230
+ end
231
+
232
+ def get_image(id)
233
+ if id.is_a? Hash then id = id['id'] end
234
+ request(:GET, "/images/#{id}")
235
+ end
236
+
237
+ def get_images(filter={})
238
+ result = request(:GET, '/images')
239
+ result.select! do |img| match_filter(img, filter) end if !filter.empty?
240
+ result
241
+ end
242
+
243
+ def update_image(image)
244
+ request(:PUT, "/images/#{image['id']}", image)
245
+ end
246
+
247
+ def delete_image(id)
248
+ if id.is_a? Hash then id = id['id'] end
249
+ request(:DELETE, "/images/#{id}")
250
+ end
251
+
252
+ def get_keypair(id)
253
+ if id.is_a? Hash then id = id['id'] end
254
+ request(:GET, "/keypairs/#{id}")
255
+ end
256
+
257
+ def get_keypairs(filter={})
258
+ result = request(:GET, '/keypairs')
259
+ result.select! do |kp| match_filter(kp, filter) end if !filter.empty?
260
+ result
261
+ end
262
+
263
+ def create_keypair(keypair)
264
+ request(:POST, '/keypairs', keypair)
265
+ end
266
+
267
+ def update_keypair(keypair)
268
+ request(:PUT, "/keypairs/#{keypair['id']}", keypair)
269
+ end
270
+
271
+ def delete_keypair(id)
272
+ if id.is_a? Hash then id = id['id'] end
273
+ request(:DELETE, "/keypairs/#{id}")
274
+ end
275
+
276
+ def generate_keypair
277
+ request(:POST, '/keypairs/generate')
278
+ end
279
+
280
+ def inspect
281
+ "#<RavelloClient:#{object_id}>"
282
+ end
283
+
284
+ private
285
+
286
+ def set_url(url)
287
+ raise 'cannot change URL when connected' if !closed?
288
+ parsed = URI.parse(url.chomp '/')
289
+ raise ArgumentError, 'https is required' if parsed.scheme != "https"
290
+ @url = parsed
291
+ end
292
+
293
+ def have_credentials?
294
+ !@username.nil? && !@password.nil?
295
+ end
296
+
297
+ def do_connect()
298
+ @connection = Net::HTTP.new(url.host, url.port)
299
+ @connection.use_ssl = true
300
+ @connection.set_debug_output $stderr if $DEBUG
301
+ @connection.start
302
+ @logger.debug("connected to #{url.host}:#{url.port}")
303
+ end
304
+
305
+ def do_login()
306
+ raise "should have credentials" if not have_credentials?
307
+ @logger.debug('performing a username/password login')
308
+ @autologin = false
309
+ auth = Base64.encode64("#{@username}:#{@password}").strip
310
+ headers = [['Authorization', "Basic #{auth}"]]
311
+ response = make_request(:POST, '/login', nil, headers)
312
+ cookies = response.get_fields('Set-Cookie')
313
+ @cookies = cookies.map{ |v| v.split(';')[0]}.join(', ')
314
+ @autologin = true
315
+ @user_info = response.entity
316
+ end
317
+
318
+ def do_logout
319
+ make_request(:POST, '/logout')
320
+ @cookies = nil
321
+ end
322
+
323
+ def make_request(method, path, body=nil, headers=nil)
324
+ uri = @url.merge(@url.path + path)
325
+ case method
326
+ when :GET
327
+ request = Net::HTTP::Get.new uri
328
+ when :POST
329
+ request = Net::HTTP::Post.new uri
330
+ when :DELETE
331
+ request = Net::HTTP::Delete.new uri
332
+ when :PUT
333
+ request = Net::HTTP::Put.new uri
334
+ when :PATCH
335
+ request = New::HTTP::Patch.new uri
336
+ else
337
+ raise ArgumentError, "unknown method: #{method.to_s}"
338
+ end
339
+ request['Accept'] = 'application/json'
340
+ request['Cookie'] = @cookies unless @cookies.nil?
341
+ request['Accept-Encoding'] = 'identity' if $DEBUG
342
+ headers.each { |kv| request.add_field(kv[0], kv[1]) } unless headers.nil?
343
+ request.body = body
344
+ request.content_type = 'application/json'
345
+ do_request(request)
346
+ end
347
+
348
+ def idempotent(request)
349
+ [:GET, :HEAD, :PUT].include? request.method
350
+ end
351
+
352
+ def do_request(request)
353
+ retries = redirects = 0
354
+ while retries < @retries and redirects < @redirects do
355
+ do_connect if !connected?
356
+ do_login if @autologin && have_credentials? && !logged_in?
357
+ begin
358
+ @logger.info("request: #{request.method} #{request.path}")
359
+ response = @connection.request(request)
360
+ @logger.info("response: #{response.code} #{response.message}")
361
+ case response.code.to_i
362
+ when 200..299
363
+ body = response.read_body
364
+ ctype = response.content_type
365
+ entity = JSON.parse(body) if ctype == 'application/json'
366
+ if entity.is_a?(Hash) && !entity['id'].nil? then
367
+ if response.key? 'Content-Location'
368
+ href = URI.parse(response['Content-Location']).path
369
+ elsif response.key? 'Location'
370
+ href = URI.parse(response['Location']).path
371
+ elsif request.method == 'POST'
372
+ # missing location header with e.g. /pubkeys
373
+ href = "#{request.uri.path}/#{entity['id']}"
374
+ else
375
+ href = request.uri.path
376
+ end
377
+ entity['href'] = href.sub(/^#{@url.path}/, '')
378
+ end
379
+ response.entity = entity
380
+ when 300..399
381
+ location = response['Location']
382
+ raise "no location for #{response.code} response" if location.nil?
383
+ url = URI.parse(location)
384
+ raise "will not redirect to #{url.to_s}" if url.route_from(@url).host
385
+ request.url = url
386
+ response.entity = nil
387
+ redirects += 1
388
+ next
389
+ when 404
390
+ response.entity = nil
391
+ break
392
+ else
393
+ message = "#{response.code} #{response.message.dump} " \
394
+ "[#{response.fetch('ERROR-CODE', 'unknown')}: " \
395
+ "#{response.fetch('ERROR-MESSAGE', 'unknown')}]"
396
+ raise response.error_type.new(message, response)
397
+ end
398
+ rescue Timeout::Error => e
399
+ @logger.debug("timeout: #{e.message}")
400
+ close
401
+ retries += 1
402
+ next if idempotent request
403
+ raise "not retrying #{request.method.to_s} request"
404
+ end
405
+ break
406
+ end
407
+ raise "maximum number of retries reached" if retries == @retries
408
+ raise "maximum number of redirects reached" if redirects == @redirects
409
+ response
410
+ end
411
+
412
+ def match_filter(obj, filter)
413
+ filter.each_pair do |fkey,fvalue|
414
+ obval = obj[fkey.to_s]
415
+ if obval.nil?
416
+ return false
417
+ elsif fvalue.is_a? Hash
418
+ return false if !obval.is_a? Hash || !match_filter(obval, fvalue)
419
+ elsif fvalue != obval
420
+ return false
421
+ end
422
+ end
423
+ true
424
+ end
425
+
426
+ end
427
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ravello-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.9'
5
+ platform: ruby
6
+ authors:
7
+ - Geert Jansen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: geert.jansen@ravellosystems.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/ravello-sdk.rb
20
+ homepage: https://github.com/ravello/ravello-ruby-sdk
21
+ licenses:
22
+ - Apache-2.0
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.0.14
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: A Ruby SDK for the Ravello Systems API
44
+ test_files: []