steamcannon-deltacloud-client 0.0.9.7.1-java
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/COPYING +176 -0
- data/Rakefile +61 -0
- data/bin/deltacloudc +158 -0
- data/init.rb +20 -0
- data/lib/deltacloud.rb +586 -0
- data/lib/documentation.rb +98 -0
- data/lib/plain_formatter.rb +86 -0
- data/specs/data/images/img1.yml +4 -0
- data/specs/data/images/img2.yml +4 -0
- data/specs/data/images/img3.yml +4 -0
- data/specs/data/instances/inst0.yml +16 -0
- data/specs/data/instances/inst1.yml +9 -0
- data/specs/data/instances/inst2.yml +9 -0
- data/specs/data/storage_snapshots/snap1.yml +4 -0
- data/specs/data/storage_snapshots/snap2.yml +4 -0
- data/specs/data/storage_snapshots/snap3.yml +4 -0
- data/specs/data/storage_volumes/vol1.yml +6 -0
- data/specs/data/storage_volumes/vol2.yml +6 -0
- data/specs/data/storage_volumes/vol3.yml +6 -0
- data/specs/fixtures/images/img1.yml +4 -0
- data/specs/fixtures/images/img2.yml +4 -0
- data/specs/fixtures/images/img3.yml +4 -0
- data/specs/fixtures/instances/inst0.yml +16 -0
- data/specs/fixtures/instances/inst1.yml +9 -0
- data/specs/fixtures/instances/inst2.yml +9 -0
- data/specs/fixtures/storage_snapshots/snap1.yml +4 -0
- data/specs/fixtures/storage_snapshots/snap2.yml +4 -0
- data/specs/fixtures/storage_snapshots/snap3.yml +4 -0
- data/specs/fixtures/storage_volumes/vol1.yml +6 -0
- data/specs/fixtures/storage_volumes/vol2.yml +6 -0
- data/specs/fixtures/storage_volumes/vol3.yml +6 -0
- data/specs/hardware_profiles_spec.rb +78 -0
- data/specs/images_spec.rb +105 -0
- data/specs/initialization_spec.rb +60 -0
- data/specs/instance_states_spec.rb +78 -0
- data/specs/instances_spec.rb +191 -0
- data/specs/realms_spec.rb +64 -0
- data/specs/shared/resources.rb +30 -0
- data/specs/spec_helper.rb +52 -0
- data/specs/storage_snapshot_spec.rb +77 -0
- data/specs/storage_volume_spec.rb +89 -0
- metadata +191 -0
data/lib/deltacloud.rb
ADDED
@@ -0,0 +1,586 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2009 Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
5
|
+
# contributor license agreements. See the NOTICE file distributed with
|
6
|
+
# this work for additional information regarding copyright ownership. The
|
7
|
+
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
8
|
+
# "License"); you may not use this file except in compliance with the
|
9
|
+
# License. You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
15
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
16
|
+
# License for the specific language governing permissions and limitations
|
17
|
+
# under the License.
|
18
|
+
|
19
|
+
require 'nokogiri'
|
20
|
+
require 'rest_client'
|
21
|
+
require 'base64'
|
22
|
+
require 'logger'
|
23
|
+
|
24
|
+
module DeltaCloud
|
25
|
+
|
26
|
+
# Get a new API client instance
|
27
|
+
#
|
28
|
+
# @param [String, user_name] API user name
|
29
|
+
# @param [String, password] API password
|
30
|
+
# @param [String, user_name] API URL (eg. http://localhost:3001/api)
|
31
|
+
# @return [DeltaCloud::API]
|
32
|
+
def self.new(user_name, password, api_url, &block)
|
33
|
+
API.new(user_name, password, api_url, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Check given credentials if their are valid against
|
37
|
+
# backend cloud provider
|
38
|
+
#
|
39
|
+
# @param [String, user_name] API user name
|
40
|
+
# @param [String, password] API password
|
41
|
+
# @param [String, user_name] API URL (eg. http://localhost:3001/api)
|
42
|
+
# @return [true|false]
|
43
|
+
def self.valid_credentials?(user_name, password, api_url)
|
44
|
+
api=API.new(user_name, password, api_url)
|
45
|
+
result = false
|
46
|
+
api.request(:get, '', :force_auth => '1') do |response|
|
47
|
+
result = true if response.code.eql?(200)
|
48
|
+
end
|
49
|
+
return result
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return a API driver for specified URL
|
53
|
+
#
|
54
|
+
# @param [String, url] API URL (eg. http://localhost:3001/api)
|
55
|
+
def self.driver_name(url)
|
56
|
+
API.new(nil, nil, url).driver_name
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.define_class(name)
|
60
|
+
@defined_classes ||= []
|
61
|
+
if @defined_classes.include?(name)
|
62
|
+
self.module_eval("API::#{name}")
|
63
|
+
else
|
64
|
+
@defined_classes << name unless @defined_classes.include?(name)
|
65
|
+
API.const_set(name, Class.new)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.classes
|
70
|
+
@defined_classes || []
|
71
|
+
end
|
72
|
+
|
73
|
+
class API
|
74
|
+
attr_accessor :logger
|
75
|
+
attr_reader :api_uri, :driver_name, :api_version, :features, :entry_points
|
76
|
+
|
77
|
+
def initialize(user_name, password, api_url, opts={}, &block)
|
78
|
+
opts[:version] = true
|
79
|
+
@logger = opts[:verbose] ? Logger.new(STDERR) : []
|
80
|
+
@username, @password = user_name, password
|
81
|
+
@api_uri = URI.parse(api_url)
|
82
|
+
@features, @entry_points = {}, {}
|
83
|
+
@verbose = opts[:verbose] || false
|
84
|
+
discover_entry_points
|
85
|
+
yield self if block_given?
|
86
|
+
end
|
87
|
+
|
88
|
+
def connect(&block)
|
89
|
+
yield self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Return API hostname
|
93
|
+
def api_host; @api_uri.host ; end
|
94
|
+
|
95
|
+
# Return API port
|
96
|
+
def api_port; @api_uri.port ; end
|
97
|
+
|
98
|
+
# Return API path
|
99
|
+
def api_path; @api_uri.path ; end
|
100
|
+
|
101
|
+
# Define methods based on 'rel' attribute in entry point
|
102
|
+
# Two methods are declared: 'images' and 'image'
|
103
|
+
def declare_entry_points_methods(entry_points)
|
104
|
+
logger = @logger
|
105
|
+
API.instance_eval do
|
106
|
+
entry_points.keys.select {|k| [:instance_states].include?(k)==false }.each do |model|
|
107
|
+
define_method model do |*args|
|
108
|
+
request(:get, "/#{model}", args.first) do |response|
|
109
|
+
# Define a new class based on model name
|
110
|
+
c = DeltaCloud.define_class("#{model.to_s.classify}")
|
111
|
+
# Create collection from index operation
|
112
|
+
base_object_collection(c, model, response)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
logger << "[API] Added method #{model}\n"
|
116
|
+
define_method :"#{model.to_s.singularize}" do |*args|
|
117
|
+
request(:get, "/#{model}/#{args[0]}") do |response|
|
118
|
+
# Define a new class based on model name
|
119
|
+
c = DeltaCloud.define_class("#{model.to_s.classify}")
|
120
|
+
# Build class for returned object
|
121
|
+
base_object(c, model, response)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
logger << "[API] Added method #{model.to_s.singularize}\n"
|
125
|
+
define_method :"fetch_#{model.to_s.singularize}" do |url|
|
126
|
+
id = url.grep(/\/#{model}\/(.*)$/)
|
127
|
+
self.send(model.to_s.singularize.to_sym, $1)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def base_object_collection(c, model, response)
|
134
|
+
collection = []
|
135
|
+
Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").each do |item|
|
136
|
+
c.instance_eval do
|
137
|
+
attr_accessor :id
|
138
|
+
attr_accessor :uri
|
139
|
+
end
|
140
|
+
collection << xml_to_class(c, item)
|
141
|
+
end
|
142
|
+
return collection
|
143
|
+
end
|
144
|
+
|
145
|
+
# Add default attributes [id and href] to class
|
146
|
+
def base_object(c, model, response)
|
147
|
+
obj = nil
|
148
|
+
Nokogiri::XML(response).xpath("#{model.to_s.singularize}").each do |item|
|
149
|
+
c.instance_eval do
|
150
|
+
attr_accessor :id
|
151
|
+
attr_accessor :uri
|
152
|
+
end
|
153
|
+
obj = xml_to_class(c, item)
|
154
|
+
end
|
155
|
+
return obj
|
156
|
+
end
|
157
|
+
|
158
|
+
# Convert XML response to defined Ruby Class
|
159
|
+
def xml_to_class(c, item)
|
160
|
+
obj = c.new
|
161
|
+
# Set default attributes
|
162
|
+
obj.id = item['id']
|
163
|
+
api = self
|
164
|
+
c.instance_eval do
|
165
|
+
define_method :client do
|
166
|
+
api
|
167
|
+
end
|
168
|
+
end
|
169
|
+
obj.uri = item['href']
|
170
|
+
logger = @logger
|
171
|
+
logger << "[DC] Creating class #{obj.class.name}\n"
|
172
|
+
obj.instance_eval do
|
173
|
+
# Declare methods for all attributes in object
|
174
|
+
item.xpath('./*').each do |attribute|
|
175
|
+
# If attribute is a link to another object then
|
176
|
+
# create a method which request this object from API
|
177
|
+
if api.entry_points.keys.include?(:"#{attribute.name}s")
|
178
|
+
c.instance_eval do
|
179
|
+
define_method :"#{attribute.name.sanitize}" do
|
180
|
+
client.send(:"#{attribute.name}", attribute['id'] )
|
181
|
+
end
|
182
|
+
logger << "[DC] Added #{attribute.name} to class #{obj.class.name}\n"
|
183
|
+
end
|
184
|
+
else
|
185
|
+
# Define methods for other attributes
|
186
|
+
c.instance_eval do
|
187
|
+
case attribute.name
|
188
|
+
# When response cointains 'link' block, declare
|
189
|
+
# methods to call links inside. This is used for instance
|
190
|
+
# to dynamicaly create .stop!, .start! methods
|
191
|
+
when "actions":
|
192
|
+
actions = []
|
193
|
+
attribute.xpath('link').each do |link|
|
194
|
+
actions << [link['rel'], link[:href]]
|
195
|
+
define_method :"#{link['rel'].sanitize}!" do |*params|
|
196
|
+
client.request(:"#{link['method']}", link['href'], {}, params.first || {})
|
197
|
+
@current_state = client.send(:"#{item.name}", item['id']).state
|
198
|
+
obj.instance_eval do |o|
|
199
|
+
def state
|
200
|
+
@current_state
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
define_method :actions do
|
206
|
+
actions.collect { |a| a.first }
|
207
|
+
end
|
208
|
+
define_method :actions_urls do
|
209
|
+
urls = {}
|
210
|
+
actions.each { |a| urls[a.first] = a.last }
|
211
|
+
urls
|
212
|
+
end
|
213
|
+
# Property attribute is handled differently
|
214
|
+
when "property":
|
215
|
+
attr_accessor :"#{attribute['name'].sanitize}"
|
216
|
+
if attribute['value'] =~ /^(\d+)$/
|
217
|
+
obj.send(:"#{attribute['name'].sanitize}=",
|
218
|
+
DeltaCloud::HWP::FloatProperty.new(attribute, attribute['name']))
|
219
|
+
else
|
220
|
+
obj.send(:"#{attribute['name'].sanitize}=",
|
221
|
+
DeltaCloud::HWP::Property.new(attribute, attribute['name']))
|
222
|
+
end
|
223
|
+
# Public and private addresses are returned as Array
|
224
|
+
when "public_addresses", "private_addresses":
|
225
|
+
attr_accessor :"#{attribute.name.sanitize}"
|
226
|
+
obj.send(:"#{attribute.name.sanitize}=",
|
227
|
+
attribute.xpath('address').collect { |address| address.text })
|
228
|
+
# Value for other attributes are just returned using
|
229
|
+
# method with same name as attribute (eg. .owner_id, .state)
|
230
|
+
else
|
231
|
+
attr_accessor :"#{attribute.name.sanitize}"
|
232
|
+
obj.send(:"#{attribute.name.sanitize}=", attribute.text.convert)
|
233
|
+
logger << "[DC] Added method #{attribute.name}[#{attribute.text}] to #{obj.class.name}\n"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
return obj
|
240
|
+
end
|
241
|
+
|
242
|
+
# Get /api and parse entry points
|
243
|
+
def discover_entry_points
|
244
|
+
return if discovered?
|
245
|
+
request(:get, @api_uri.to_s) do |response|
|
246
|
+
api_xml = Nokogiri::XML(response)
|
247
|
+
@driver_name = api_xml.xpath('/api').first['driver']
|
248
|
+
@api_version = api_xml.xpath('/api').first['version']
|
249
|
+
logger << "[API] Version #{@api_version}\n"
|
250
|
+
logger << "[API] Driver #{@driver_name}\n"
|
251
|
+
api_xml.css("api > link").each do |entry_point|
|
252
|
+
rel, href = entry_point['rel'].to_sym, entry_point['href']
|
253
|
+
@entry_points.store(rel, href)
|
254
|
+
logger << "[API] Entry point '#{rel}' added\n"
|
255
|
+
entry_point.css("feature").each do |feature|
|
256
|
+
@features[rel] ||= []
|
257
|
+
@features[rel] << feature['name'].to_sym
|
258
|
+
logger << "[API] Feature #{feature['name']} added to #{rel}\n"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
declare_entry_points_methods(@entry_points)
|
263
|
+
end
|
264
|
+
|
265
|
+
def create_key(opts={}, &block)
|
266
|
+
params = { :name => opts[:name] }
|
267
|
+
key = nil
|
268
|
+
request(:post, entry_points[:keys], {}, params) do |response|
|
269
|
+
c = DeltaCloud.define_class("Key")
|
270
|
+
key = base_object(c, :key, response)
|
271
|
+
yield key if block_given?
|
272
|
+
end
|
273
|
+
return key
|
274
|
+
end
|
275
|
+
|
276
|
+
# Create a new instance, using image +image_id+. Possible optiosn are
|
277
|
+
#
|
278
|
+
# name - a user-defined name for the instance
|
279
|
+
# realm - a specific realm for placement of the instance
|
280
|
+
# hardware_profile - either a string giving the name of the
|
281
|
+
# hardware profile or a hash. The hash must have an
|
282
|
+
# entry +id+, giving the id of the hardware profile,
|
283
|
+
# and may contain additional names of properties,
|
284
|
+
# e.g. 'storage', to override entries in the
|
285
|
+
# hardware profile
|
286
|
+
def create_instance(image_id, opts={}, &block)
|
287
|
+
name = opts[:name]
|
288
|
+
realm_id = opts[:realm]
|
289
|
+
user_data = opts[:user_data]
|
290
|
+
key_name = opts[:key_name]
|
291
|
+
|
292
|
+
params = opts.dup
|
293
|
+
( params[:realm_id] = realm_id ) if realm_id
|
294
|
+
( params[:name] = name ) if name
|
295
|
+
( params[:user_data] = user_data ) if user_data
|
296
|
+
( params[:keyname] = user_data ) if key_name
|
297
|
+
|
298
|
+
if opts[:hardware_profile].is_a?(String)
|
299
|
+
params[:hwp_id] = opts[:hardware_profile]
|
300
|
+
elsif opts[:hardware_profile].is_a?(Hash)
|
301
|
+
params.delete(:hardware_profile)
|
302
|
+
opts[:hardware_profile].each do |k,v|
|
303
|
+
params[:"hwp_#{k}"] = v
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
params[:image_id] = image_id
|
308
|
+
instance = nil
|
309
|
+
|
310
|
+
request(:post, entry_points[:instances], {}, params) do |response|
|
311
|
+
c = DeltaCloud.define_class("Instance")
|
312
|
+
instance = base_object(c, :instance, response)
|
313
|
+
yield instance if block_given?
|
314
|
+
end
|
315
|
+
|
316
|
+
return instance
|
317
|
+
end
|
318
|
+
|
319
|
+
|
320
|
+
def create_storage_volume(opts={}, &block)
|
321
|
+
params = opts.dup
|
322
|
+
params[:realm_id] ||= params.delete(:realm)
|
323
|
+
storage_volume = nil
|
324
|
+
|
325
|
+
request(:post, entry_points[:storage_volumes], {}, params) do |response|
|
326
|
+
c = DeltaCloud.define_class("StorageVolume")
|
327
|
+
storage_volume = base_object(c, :storage_volume, response)
|
328
|
+
yield storage_volume if block_given?
|
329
|
+
end
|
330
|
+
|
331
|
+
storage_volume
|
332
|
+
end
|
333
|
+
|
334
|
+
# Basic request method
|
335
|
+
#
|
336
|
+
def request(*args, &block)
|
337
|
+
conf = {
|
338
|
+
:method => (args[0] || 'get').to_sym,
|
339
|
+
:path => (args[1]=~/^http/) ? args[1] : "#{api_uri.to_s}#{args[1]}",
|
340
|
+
:query_args => args[2] || {},
|
341
|
+
:form_data => args[3] || {}
|
342
|
+
}
|
343
|
+
if conf[:query_args] != {}
|
344
|
+
conf[:path] += '?' + URI.escape(conf[:query_args].collect{ |key, value| "#{key}=#{value}" }.join('&')).to_s
|
345
|
+
end
|
346
|
+
logger << "[#{conf[:method].to_s.upcase}] #{conf[:path]}\n"
|
347
|
+
if conf[:method].eql?(:post)
|
348
|
+
RestClient.send(:post, conf[:path], conf[:form_data], default_headers) do |response, request, block|
|
349
|
+
if response.respond_to?('body')
|
350
|
+
yield response.body if block_given?
|
351
|
+
else
|
352
|
+
yield response.to_s if block_given?
|
353
|
+
end
|
354
|
+
end
|
355
|
+
else
|
356
|
+
RestClient.send(conf[:method], conf[:path], default_headers) do |response, request, block|
|
357
|
+
if response.respond_to?('body')
|
358
|
+
yield response.body if block_given?
|
359
|
+
else
|
360
|
+
yield response.to_s if block_given?
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# Check if specified collection have wanted feature
|
367
|
+
def feature?(collection, name)
|
368
|
+
@features.has_key?(collection) && @features[collection].include?(name)
|
369
|
+
end
|
370
|
+
|
371
|
+
# List available instance states and transitions between them
|
372
|
+
def instance_states
|
373
|
+
states = []
|
374
|
+
request(:get, entry_points[:instance_states]) do |response|
|
375
|
+
Nokogiri::XML(response).xpath('states/state').each do |state_el|
|
376
|
+
state = DeltaCloud::InstanceState::State.new(state_el['name'])
|
377
|
+
state_el.xpath('transition').each do |transition_el|
|
378
|
+
state.transitions << DeltaCloud::InstanceState::Transition.new(
|
379
|
+
transition_el['to'],
|
380
|
+
transition_el['action']
|
381
|
+
)
|
382
|
+
end
|
383
|
+
states << state
|
384
|
+
end
|
385
|
+
end
|
386
|
+
states
|
387
|
+
end
|
388
|
+
|
389
|
+
# Select instance state specified by name
|
390
|
+
def instance_state(name)
|
391
|
+
instance_states.select { |s| s.name.to_s.eql?(name.to_s) }.first
|
392
|
+
end
|
393
|
+
|
394
|
+
# Skip parsing /api when we already got entry points
|
395
|
+
def discovered?
|
396
|
+
true if @entry_points!={}
|
397
|
+
end
|
398
|
+
|
399
|
+
def documentation(collection, operation=nil)
|
400
|
+
data = {}
|
401
|
+
request(:get, "/docs/#{collection}") do |body|
|
402
|
+
document = Nokogiri::XML(body)
|
403
|
+
if operation
|
404
|
+
data[:operation] = operation
|
405
|
+
data[:description] = document.xpath('/docs/collection/operations/operation[@name = "'+operation+'"]/description').first.text.strip
|
406
|
+
return false unless data[:description]
|
407
|
+
data[:params] = []
|
408
|
+
(document/"/docs/collection/operations/operation[@name='#{operation}']/parameter").each do |param|
|
409
|
+
data[:params] << {
|
410
|
+
:name => param['name'],
|
411
|
+
:required => param['type'] == 'optional',
|
412
|
+
:type => (param/'class').text
|
413
|
+
}
|
414
|
+
end
|
415
|
+
else
|
416
|
+
data[:description] = (document/'/docs/collection/description').text
|
417
|
+
data[:collection] = collection
|
418
|
+
data[:operations] = (document/"/docs/collection/operations/operation").collect{ |o| o['name'] }
|
419
|
+
end
|
420
|
+
end
|
421
|
+
return Documentation.new(self, data)
|
422
|
+
end
|
423
|
+
|
424
|
+
private
|
425
|
+
|
426
|
+
def default_headers
|
427
|
+
# The linebreaks inserted every 60 characters in the Base64
|
428
|
+
# encoded header cause problems under JRuby
|
429
|
+
auth_header = "Basic "+Base64.encode64("#{@username}:#{@password}")
|
430
|
+
auth_header.gsub!("\n", "")
|
431
|
+
{
|
432
|
+
:authorization => auth_header,
|
433
|
+
:accept => "application/xml"
|
434
|
+
}
|
435
|
+
end
|
436
|
+
|
437
|
+
end
|
438
|
+
|
439
|
+
class Documentation
|
440
|
+
|
441
|
+
attr_reader :api, :description, :params, :collection_operations
|
442
|
+
attr_reader :collection, :operation
|
443
|
+
|
444
|
+
def initialize(api, opts={})
|
445
|
+
@description, @api = opts[:description], api
|
446
|
+
@params = parse_parameters(opts[:params]) if opts[:params]
|
447
|
+
@collection_operations = opts[:operations] if opts[:operations]
|
448
|
+
@collection = opts[:collection]
|
449
|
+
@operation = opts[:operation]
|
450
|
+
self
|
451
|
+
end
|
452
|
+
|
453
|
+
def operations
|
454
|
+
@collection_operations.collect { |o| api.documentation(@collection, o) }
|
455
|
+
end
|
456
|
+
|
457
|
+
class OperationParameter
|
458
|
+
attr_reader :name
|
459
|
+
attr_reader :type
|
460
|
+
attr_reader :required
|
461
|
+
attr_reader :description
|
462
|
+
|
463
|
+
def initialize(data)
|
464
|
+
@name, @type, @required, @description = data[:name], data[:type], data[:required], data[:description]
|
465
|
+
end
|
466
|
+
|
467
|
+
def to_comment
|
468
|
+
" # @param [#{@type}, #{@name}] #{@description}"
|
469
|
+
end
|
470
|
+
|
471
|
+
end
|
472
|
+
|
473
|
+
private
|
474
|
+
|
475
|
+
def parse_parameters(params)
|
476
|
+
params.collect { |p| OperationParameter.new(p) }
|
477
|
+
end
|
478
|
+
|
479
|
+
end
|
480
|
+
|
481
|
+
module InstanceState
|
482
|
+
|
483
|
+
class State
|
484
|
+
attr_reader :name
|
485
|
+
attr_reader :transitions
|
486
|
+
|
487
|
+
def initialize(name)
|
488
|
+
@name, @transitions = name, []
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
class Transition
|
493
|
+
attr_reader :to
|
494
|
+
attr_reader :action
|
495
|
+
|
496
|
+
def initialize(to, action)
|
497
|
+
@to = to
|
498
|
+
@action = action
|
499
|
+
end
|
500
|
+
|
501
|
+
def auto?
|
502
|
+
@action.nil?
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
module HWP
|
508
|
+
|
509
|
+
class Property
|
510
|
+
attr_reader :name, :unit, :value, :kind
|
511
|
+
|
512
|
+
def initialize(xml, name)
|
513
|
+
@name, @kind, @value, @unit = xml['name'], xml['kind'].to_sym, xml['value'], xml['unit']
|
514
|
+
declare_ranges(xml)
|
515
|
+
self
|
516
|
+
end
|
517
|
+
|
518
|
+
def present?
|
519
|
+
! @value.nil?
|
520
|
+
end
|
521
|
+
|
522
|
+
private
|
523
|
+
|
524
|
+
def declare_ranges(xml)
|
525
|
+
case xml['kind']
|
526
|
+
when 'range':
|
527
|
+
self.class.instance_eval do
|
528
|
+
attr_reader :range
|
529
|
+
end
|
530
|
+
@range = { :from => xml.xpath('range').first['first'], :to => xml.xpath('range').first['last'] }
|
531
|
+
when 'enum':
|
532
|
+
self.class.instance_eval do
|
533
|
+
attr_reader :options
|
534
|
+
end
|
535
|
+
@options = xml.xpath('enum/entry').collect { |e| e['value'] }
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
end
|
540
|
+
|
541
|
+
# FloatProperty is like Property but return value is Float instead of String.
|
542
|
+
class FloatProperty < Property
|
543
|
+
def initialize(xml, name)
|
544
|
+
super(xml, name)
|
545
|
+
@value = @value.to_f if @value
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
end
|
551
|
+
|
552
|
+
class String
|
553
|
+
|
554
|
+
unless method_defined?(:classify)
|
555
|
+
# Create a class name from string
|
556
|
+
def classify
|
557
|
+
self.singularize.camelize
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
unless method_defined?(:camelize)
|
562
|
+
# Camelize converts strings to UpperCamelCase
|
563
|
+
def camelize
|
564
|
+
self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
unless method_defined?(:singularize)
|
569
|
+
# Strip 's' character from end of string
|
570
|
+
def singularize
|
571
|
+
self.gsub(/s$/, '')
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
# Convert string to float if string value seems like Float
|
576
|
+
def convert
|
577
|
+
return self.to_f if self.strip =~ /^([\d\.]+$)/
|
578
|
+
self
|
579
|
+
end
|
580
|
+
|
581
|
+
# Simply converts whitespaces and - symbols to '_' which is safe for Ruby
|
582
|
+
def sanitize
|
583
|
+
self.gsub(/(\W+)/, '_')
|
584
|
+
end
|
585
|
+
|
586
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'lib/deltacloud'
|
2
|
+
|
3
|
+
skip_methods = [ "id=", "uri=" ]
|
4
|
+
|
5
|
+
begin
|
6
|
+
@dc=DeltaCloud.new('mockuser', 'mockpassword', 'http://localhost:3001/api')
|
7
|
+
rescue
|
8
|
+
puts "Please make sure that Deltacloud API is running with Mock driver"
|
9
|
+
exit(1)
|
10
|
+
end
|
11
|
+
|
12
|
+
@dc.entry_points.keys.each do |ep|
|
13
|
+
@dc.send(ep)
|
14
|
+
end
|
15
|
+
|
16
|
+
class_list = DeltaCloud::classes.collect { |c| DeltaCloud::module_eval("::DeltaCloud::API::#{c}")}
|
17
|
+
|
18
|
+
def read_method_description(c, method)
|
19
|
+
if method =~ /es$/
|
20
|
+
" # Read #{c.downcase} collection from Deltacloud API"
|
21
|
+
else
|
22
|
+
case method
|
23
|
+
when "uri"
|
24
|
+
" # Return URI to API for this object"
|
25
|
+
when "action_urls"
|
26
|
+
" # Return available actions API URL"
|
27
|
+
when "client"
|
28
|
+
" # Return instance of API client"
|
29
|
+
else
|
30
|
+
" # Get #{method} attribute value from #{c.downcase}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def read_parameters(c, method)
|
36
|
+
out = []
|
37
|
+
if method =~ /es$/
|
38
|
+
out << " # @param [String, #id] Filter by ID"
|
39
|
+
end
|
40
|
+
out.join("\n")
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_return_value(c, method)
|
44
|
+
if method =~ /es$/
|
45
|
+
rt = "Array"
|
46
|
+
else
|
47
|
+
rt = "String"
|
48
|
+
end
|
49
|
+
" # @return [String] Value of #{method}"
|
50
|
+
end
|
51
|
+
|
52
|
+
out = []
|
53
|
+
|
54
|
+
class_list.each do |c|
|
55
|
+
class_name = "#{c}".gsub(/^DeltaCloud::/, '')
|
56
|
+
out << "module DeltaCloud"
|
57
|
+
out << " class API"
|
58
|
+
@dc.entry_points.keys.each do |ep|
|
59
|
+
out << "# Return #{ep.to_s.classify} object with given id\n"
|
60
|
+
out << "# "
|
61
|
+
out << "# #{@dc.documentation(ep.to_s).description.split("\n").join("\n# ")}"
|
62
|
+
out << "# @return [#{ep.to_s.classify}]"
|
63
|
+
out << "def #{ep.to_s.gsub(/s$/, '')}"
|
64
|
+
out << "end"
|
65
|
+
out << "# Return collection of #{ep.to_s.classify} objects"
|
66
|
+
out << "# "
|
67
|
+
out << "# #{@dc.documentation(ep.to_s).description.split("\n").join("\n# ")}"
|
68
|
+
@dc.documentation(ep.to_s, 'index').params.each do |p|
|
69
|
+
out << p.to_comment
|
70
|
+
end
|
71
|
+
out << "# @return [Array] [#{ep.to_s.classify}]"
|
72
|
+
out << "def #{ep}(opts={})"
|
73
|
+
out << "end"
|
74
|
+
end
|
75
|
+
out << " end"
|
76
|
+
out << " class #{class_name}"
|
77
|
+
c.instance_methods(false).each do |method|
|
78
|
+
next if skip_methods.include?(method)
|
79
|
+
params = read_parameters(class_name, method)
|
80
|
+
retval = read_return_value(class_name, method)
|
81
|
+
out << read_method_description(class_name, method)
|
82
|
+
out << params if params
|
83
|
+
out << retval if retval
|
84
|
+
out << " def #{method}"
|
85
|
+
out << " # This method was generated dynamically from API"
|
86
|
+
out << " end\n"
|
87
|
+
end
|
88
|
+
out << " end"
|
89
|
+
out << "end"
|
90
|
+
end
|
91
|
+
|
92
|
+
FileUtils.rm_r('doc') rescue nil
|
93
|
+
FileUtils.mkdir_p('doc')
|
94
|
+
File.open('doc/deltacloud.rb', 'w') do |f|
|
95
|
+
f.puts(out.join("\n"))
|
96
|
+
end
|
97
|
+
system("yardoc -m markdown --readme README --title 'Deltacloud Client Library' 'lib/*.rb' 'doc/deltacloud.rb' --verbose")
|
98
|
+
FileUtils.rm('doc/deltacloud.rb')
|