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.
- checksums.yaml +7 -0
- data/lib/ravello-sdk.rb +427 -0
- 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
|
data/lib/ravello-sdk.rb
ADDED
|
@@ -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: []
|