deltacloud-client 0.0.4 → 0.0.5
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/Rakefile +11 -7
- data/bin/deltacloudc +9 -6
- data/lib/deltacloud.rb +412 -322
- data/lib/documentation.rb +97 -0
- data/lib/plain_formatter.rb +86 -0
- data/specs/instances_spec.rb +14 -14
- data/specs/realms_spec.rb +0 -2
- data/specs/spec_helper.rb +2 -5
- data/specs/storage_volume_spec.rb +2 -2
- metadata +20 -18
- data/credentials.yml +0 -2
- data/lib/dcloud/base_model.rb +0 -80
- data/lib/dcloud/hardware_profile.rb +0 -124
- data/lib/dcloud/image.rb +0 -56
- data/lib/dcloud/instance.rb +0 -142
- data/lib/dcloud/realm.rb +0 -57
- data/lib/dcloud/state.rb +0 -30
- data/lib/dcloud/storage_snapshot.rb +0 -59
- data/lib/dcloud/storage_volume.rb +0 -63
- data/lib/dcloud/transition.rb +0 -35
data/Rakefile
CHANGED
@@ -16,20 +16,24 @@
|
|
16
16
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
17
17
|
|
18
18
|
require 'rake/gempackagetask'
|
19
|
-
require 'spec/rake/spectask'
|
20
19
|
|
21
20
|
load 'deltacloud-client.gemspec'
|
22
21
|
|
22
|
+
desc "Generate documentation"
|
23
|
+
task 'documentation' do
|
24
|
+
load 'lib/documentation.rb'
|
25
|
+
end
|
26
|
+
|
23
27
|
Rake::GemPackageTask.new(@spec) do |pkg|
|
24
28
|
pkg.need_tar = true
|
25
29
|
end
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
'
|
32
|
-
|
31
|
+
if Gem.available?('rspec')
|
32
|
+
require 'spec/rake/spectask'
|
33
|
+
desc "Run all examples"
|
34
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
35
|
+
t.spec_files = FileList['specs/**/*_spec.rb']
|
36
|
+
end
|
33
37
|
end
|
34
38
|
|
35
39
|
desc "Setup Fixtures"
|
data/bin/deltacloudc
CHANGED
@@ -19,7 +19,10 @@
|
|
19
19
|
require 'rubygems'
|
20
20
|
require 'optparse'
|
21
21
|
require 'uri'
|
22
|
-
require 'deltacloud'
|
22
|
+
require 'lib/deltacloud'
|
23
|
+
require 'lib/plain_formatter'
|
24
|
+
|
25
|
+
include DeltaCloud::PlainFormatter
|
23
26
|
|
24
27
|
options = {
|
25
28
|
:verbose => false
|
@@ -66,7 +69,7 @@ options[:collection] = ARGV[0]
|
|
66
69
|
options[:operation] = ARGV[1]
|
67
70
|
|
68
71
|
# Connect to Deltacloud API and fetch all entry points
|
69
|
-
client = DeltaCloud.new(url.user || ENV['API_USER'], url.password || ENV['API_PASSWORD'], api_url
|
72
|
+
client = DeltaCloud.new(url.user || ENV['API_USER'], url.password || ENV['API_PASSWORD'], api_url)
|
70
73
|
collections = client.entry_points.keys
|
71
74
|
|
72
75
|
# Exclude collection which don't have methods in client library yet
|
@@ -106,7 +109,7 @@ if options[:collection] and ( options[:operation].nil? or options[:operation].eq
|
|
106
109
|
params.merge!(:id => options[:id]) if options[:id]
|
107
110
|
params.merge!(:state => options[:state]) if options[:state]
|
108
111
|
client.send(options[:collection].to_s, params).each do |model|
|
109
|
-
puts model
|
112
|
+
puts format(model)
|
110
113
|
end
|
111
114
|
exit(0)
|
112
115
|
end
|
@@ -121,7 +124,7 @@ if options[:collection] and options[:operation]
|
|
121
124
|
# If collection is set and requested operation is 'show' just 'singularize'
|
122
125
|
# collection name and print item with specified id (-i parameter)
|
123
126
|
if options[:operation].eql?('show')
|
124
|
-
puts client.send(options[:collection].gsub(/s$/, ''), options[:id]
|
127
|
+
puts format(client.send(options[:collection].gsub(/s$/, ''), options[:id]))
|
125
128
|
exit(0)
|
126
129
|
end
|
127
130
|
|
@@ -137,7 +140,7 @@ if options[:collection] and options[:operation]
|
|
137
140
|
params.merge!(:image_id => options[:image_id]) if options[:image_id]
|
138
141
|
params.merge!(:hwp_id => options[:hwp_id]) if options[:hwp_id]
|
139
142
|
instance = client.create_instance(options[:image_id], params)
|
140
|
-
puts instance
|
143
|
+
puts format(instance)
|
141
144
|
exit(0)
|
142
145
|
end
|
143
146
|
|
@@ -146,7 +149,7 @@ if options[:collection] and options[:operation]
|
|
146
149
|
instance = client.instance(options[:id])
|
147
150
|
instance.send("#{options[:operation]}!".to_s)
|
148
151
|
instance = client.instance(options[:id])
|
149
|
-
puts instance
|
152
|
+
puts format(instance)
|
150
153
|
exit(0)
|
151
154
|
end
|
152
155
|
end
|
data/lib/deltacloud.rb
CHANGED
@@ -15,404 +15,494 @@
|
|
15
15
|
# License along with this library; if not, write to the Free Software
|
16
16
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
17
17
|
|
18
|
+
require 'nokogiri'
|
18
19
|
require 'rest_client'
|
19
|
-
require 'rexml/document'
|
20
|
-
require 'logger'
|
21
|
-
require 'dcloud/hardware_profile'
|
22
|
-
require 'dcloud/realm'
|
23
|
-
require 'dcloud/image'
|
24
|
-
require 'dcloud/instance'
|
25
|
-
require 'dcloud/storage_volume'
|
26
|
-
require 'dcloud/storage_snapshot'
|
27
|
-
require 'dcloud/state'
|
28
|
-
require 'dcloud/transition'
|
29
20
|
require 'base64'
|
21
|
+
require 'logger'
|
30
22
|
|
31
|
-
|
23
|
+
module DeltaCloud
|
32
24
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
25
|
+
# Get a new API client instance
|
26
|
+
#
|
27
|
+
# @param [String, user_name] API user name
|
28
|
+
# @param [String, password] API password
|
29
|
+
# @param [String, user_name] API URL (eg. http://localhost:3001/api)
|
30
|
+
# @return [DeltaCloud::API]
|
31
|
+
def self.new(user_name, password, api_url, &block)
|
32
|
+
API.new(user_name, password, api_url, &block)
|
33
|
+
end
|
39
34
|
|
35
|
+
# Return a API driver for specified URL
|
36
|
+
#
|
37
|
+
# @param [String, url] API URL (eg. http://localhost:3001/api)
|
40
38
|
def self.driver_name(url)
|
41
|
-
|
42
|
-
|
39
|
+
API.new(nil, nil, url).driver_name
|
40
|
+
end
|
41
|
+
|
42
|
+
class API
|
43
|
+
attr_accessor :logger
|
44
|
+
attr_reader :api_uri, :driver_name, :api_version, :features, :entry_points
|
45
|
+
attr_reader :classes
|
46
|
+
|
47
|
+
def initialize(user_name, password, api_url, opts={}, &block)
|
48
|
+
@logger = opts[:verbose] ? Logger.new(STDERR) : []
|
49
|
+
@username, @password = user_name, password
|
50
|
+
@api_uri = URI.parse(api_url)
|
51
|
+
@features, @entry_points = {}, {}
|
52
|
+
@classes = []
|
53
|
+
@verbose = opts[:verbose] || false
|
54
|
+
discover_entry_points
|
55
|
+
yield self if block_given?
|
43
56
|
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def initialize(name, password, api_uri, opts={}, &block)
|
47
|
-
@logger = Logger.new( STDERR )
|
48
|
-
@name = name
|
49
|
-
@password = password
|
50
|
-
@api_uri = URI.parse( api_uri )
|
51
|
-
@entry_points = {}
|
52
|
-
@verbose = opts[:verbose]
|
53
|
-
@features = {}
|
54
|
-
discover_entry_points
|
55
|
-
connect( &block )
|
56
|
-
self
|
57
|
-
end
|
58
|
-
|
59
|
-
|
60
|
-
def connect(&block)
|
61
|
-
@http = RestClient::Resource.new( api_uri.to_s , :accept => 'application/xml' )
|
62
|
-
discover_entry_points
|
63
|
-
block.call( self ) if block
|
64
|
-
self
|
65
|
-
end
|
66
|
-
|
67
|
-
def api_host
|
68
|
-
@api_uri.host
|
69
|
-
end
|
70
|
-
|
71
|
-
def api_port
|
72
|
-
@api_uri.port
|
73
|
-
end
|
74
57
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
def feature?(collection, name)
|
80
|
-
@features.has_key?(collection) && @features[collection].include?(name)
|
81
|
-
end
|
58
|
+
def connect(&block)
|
59
|
+
yield self
|
60
|
+
end
|
82
61
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
62
|
+
# Return API hostname
|
63
|
+
def api_host; @api_uri.host ; end
|
64
|
+
|
65
|
+
# Return API port
|
66
|
+
def api_port; @api_uri.port ; end
|
67
|
+
|
68
|
+
# Return API path
|
69
|
+
def api_path; @api_uri.path ; end
|
70
|
+
|
71
|
+
# Define methods based on 'rel' attribute in entry point
|
72
|
+
# Two methods are declared: 'images' and 'image'
|
73
|
+
def declare_entry_points_methods(entry_points)
|
74
|
+
logger = @logger
|
75
|
+
API.instance_eval do
|
76
|
+
entry_points.keys.select {|k| [:instance_states].include?(k)==false }.each do |model|
|
77
|
+
define_method model do |*args|
|
78
|
+
request(:get, "/#{model}", args.first) do |response|
|
79
|
+
# Define a new class based on model name
|
80
|
+
c = Kernel.define_class("#{model.to_s.classify}")
|
81
|
+
# Create collection from index operation
|
82
|
+
base_object_collection(c, model, response)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
logger << "[API] Added method #{model}\n"
|
86
|
+
define_method :"#{model.to_s.singularize}" do |*args|
|
87
|
+
request(:get, "/#{model}/#{args[0]}") do |response|
|
88
|
+
# Define a new class based on model name
|
89
|
+
c = Kernel.define_class("#{model.to_s.classify}")
|
90
|
+
# Build class for returned object
|
91
|
+
base_object(c, model, response)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
logger << "[API] Added method #{model.to_s.singularize}\n"
|
95
|
+
define_method :"fetch_#{model.to_s.singularize}" do |url|
|
96
|
+
id = url.grep(/\/#{model}\/(.*)$/)
|
97
|
+
self.send(model.to_s.singularize.to_sym, $1)
|
98
|
+
end
|
99
|
+
end
|
90
100
|
end
|
91
101
|
end
|
92
|
-
hardware_profiles
|
93
|
-
end
|
94
102
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
103
|
+
def base_object_collection(c, model, response)
|
104
|
+
collection = []
|
105
|
+
Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").each do |item|
|
106
|
+
c.instance_eval do
|
107
|
+
attr_accessor :id
|
108
|
+
attr_accessor :uri
|
109
|
+
end
|
110
|
+
collection << xml_to_class(c, item)
|
101
111
|
end
|
112
|
+
return collection
|
102
113
|
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def fetch_hardware_profile(uri)
|
106
|
-
xml = fetch_resource( :hardware_profile, uri )
|
107
|
-
return DCloud::HardwareProfile.new( self, uri, xml ) if xml
|
108
|
-
nil
|
109
|
-
end
|
110
114
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
115
|
+
# Add default attributes [id and href] to class
|
116
|
+
def base_object(c, model, response)
|
117
|
+
obj = nil
|
118
|
+
Nokogiri::XML(response).xpath("#{model.to_s.singularize}").each do |item|
|
119
|
+
c.instance_eval do
|
120
|
+
attr_accessor :id
|
121
|
+
attr_accessor :uri
|
122
|
+
end
|
123
|
+
obj = xml_to_class(c, item)
|
116
124
|
end
|
125
|
+
return obj
|
117
126
|
end
|
118
|
-
nil
|
119
|
-
end
|
120
127
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
:
|
129
|
-
|
130
|
-
doc.get_elements('docs/collection/operations/operation').each do |operation|
|
131
|
-
p = {}
|
132
|
-
p[:name] = operation.attributes['name']
|
133
|
-
p[:description] = operation.get_elements('description').first.text
|
134
|
-
p[:parameters] = []
|
135
|
-
operation.get_elements('parameter').each do |param|
|
136
|
-
p[:parameters] << param.attributes['name']
|
128
|
+
# Convert XML response to defined Ruby Class
|
129
|
+
def xml_to_class(c, item)
|
130
|
+
obj = c.new
|
131
|
+
# Set default attributes
|
132
|
+
obj.id = item['id']
|
133
|
+
api = self
|
134
|
+
c.instance_eval do
|
135
|
+
define_method :client do
|
136
|
+
api
|
137
137
|
end
|
138
|
-
docs[:operations] << p
|
139
138
|
end
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
139
|
+
obj.uri = item['href']
|
140
|
+
logger = @logger
|
141
|
+
logger << "[DC] Creating class #{obj.class.name}\n"
|
142
|
+
obj.instance_eval do
|
143
|
+
# Declare methods for all attributes in object
|
144
|
+
item.xpath('./*').each do |attribute|
|
145
|
+
# If attribute is a link to another object then
|
146
|
+
# create a method which request this object from API
|
147
|
+
if api.entry_points.keys.include?(:"#{attribute.name}s")
|
148
|
+
c.instance_eval do
|
149
|
+
define_method :"#{attribute.name.sanitize}" do
|
150
|
+
client.send(:"#{attribute.name}", attribute['id'] )
|
151
|
+
end
|
152
|
+
logger << "[DC] Added #{attribute.name} to class #{obj.class.name}\n"
|
153
|
+
end
|
154
|
+
else
|
155
|
+
# Define methods for other attributes
|
156
|
+
c.instance_eval do
|
157
|
+
case attribute.name
|
158
|
+
# When response cointains 'link' block, declare
|
159
|
+
# methods to call links inside. This is used for instance
|
160
|
+
# to dynamicaly create .stop!, .start! methods
|
161
|
+
when "actions":
|
162
|
+
actions = []
|
163
|
+
attribute.xpath('link').each do |link|
|
164
|
+
actions << [link['rel'], link[:href]]
|
165
|
+
define_method :"#{link['rel'].sanitize}!" do
|
166
|
+
client.request(:"#{link['method']}", link['href'], {}, {})
|
167
|
+
client.send(:"#{item.name}", item['id'])
|
168
|
+
end
|
169
|
+
end
|
170
|
+
define_method :actions do
|
171
|
+
actions.collect { |a| a.first }
|
172
|
+
end
|
173
|
+
define_method :actions_urls do
|
174
|
+
urls = {}
|
175
|
+
actions.each { |a| urls[a.first] = a.last }
|
176
|
+
urls
|
177
|
+
end
|
178
|
+
# Property attribute is handled differently
|
179
|
+
when "property":
|
180
|
+
define_method :"#{attribute['name'].sanitize}" do
|
181
|
+
if attribute['value'] =~ /^(\d+)$/
|
182
|
+
DeltaCloud::HWP::FloatProperty.new(attribute, attribute['name'])
|
183
|
+
else
|
184
|
+
DeltaCloud::HWP::Property.new(attribute, attribute['name'])
|
185
|
+
end
|
186
|
+
end
|
187
|
+
# Public and private addresses are returned as Array
|
188
|
+
when "public_addresses", "private_addresses":
|
189
|
+
define_method :"#{attribute.name.sanitize}" do
|
190
|
+
attribute.xpath('address').collect { |address| address.text }
|
191
|
+
end
|
192
|
+
# Value for other attributes are just returned using
|
193
|
+
# method with same name as attribute (eg. .owner_id, .state)
|
194
|
+
else
|
195
|
+
define_method :"#{attribute.name.sanitize}" do
|
196
|
+
attribute.text.convert
|
197
|
+
end
|
198
|
+
logger << "[DC] Added method #{attribute.name} to #{obj.class.name}\n"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
148
203
|
end
|
204
|
+
add_class_record(obj)
|
205
|
+
return obj
|
149
206
|
end
|
150
|
-
docs
|
151
|
-
end
|
152
207
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
208
|
+
# Get /api and parse entry points
|
209
|
+
def discover_entry_points
|
210
|
+
return if discovered?
|
211
|
+
request(:get, @api_uri.to_s) do |response|
|
212
|
+
api_xml = Nokogiri::XML(response)
|
213
|
+
@driver_name = api_xml.xpath('/api').first['driver']
|
214
|
+
@api_version = api_xml.xpath('/api').first['version']
|
215
|
+
logger << "[API] Version #{@api_version}\n"
|
216
|
+
logger << "[API] Driver #{@driver_name}\n"
|
217
|
+
api_xml.css("api > link").each do |entry_point|
|
218
|
+
rel, href = entry_point['rel'].to_sym, entry_point['href']
|
219
|
+
@entry_points.store(rel, href)
|
220
|
+
logger << "[API] Entry point '#{rel}' added\n"
|
221
|
+
entry_point.css("feature").each do |feature|
|
222
|
+
@features[rel] ||= []
|
223
|
+
@features[rel] << feature['name'].to_sym
|
224
|
+
logger << "[API] Feature #{feature['name']} added to #{rel}\n"
|
225
|
+
end
|
164
226
|
end
|
165
|
-
states << state
|
166
227
|
end
|
228
|
+
declare_entry_points_methods(@entry_points)
|
167
229
|
end
|
168
|
-
states
|
169
|
-
end
|
170
230
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
231
|
+
# Create a new instance, using image +image_id+. Possible optiosn are
|
232
|
+
#
|
233
|
+
# name - a user-defined name for the instance
|
234
|
+
# realm - a specific realm for placement of the instance
|
235
|
+
# hardware_profile - either a string giving the name of the
|
236
|
+
# hardware profile or a hash. The hash must have an
|
237
|
+
# entry +id+, giving the id of the hardware profile,
|
238
|
+
# and may contain additional names of properties,
|
239
|
+
# e.g. 'storage', to override entries in the
|
240
|
+
# hardware profile
|
241
|
+
def create_instance(image_id, opts={}, &block)
|
242
|
+
name = opts[:name]
|
243
|
+
realm_id = opts[:realm]
|
244
|
+
user_data = opts[:user_data]
|
245
|
+
|
246
|
+
params = {}
|
247
|
+
( params[:realm_id] = realm_id ) if realm_id
|
248
|
+
( params[:name] = name ) if name
|
249
|
+
( params[:user_data] = user_data ) if user_data
|
250
|
+
|
251
|
+
if opts[:hardware_profile].is_a?(String)
|
252
|
+
params[:hwp_id] = opts[:hardware_profile]
|
253
|
+
elsif opts[:hardware_profile].is_a?(Hash)
|
254
|
+
opts[:hardware_profile].each do |k,v|
|
255
|
+
params[:"hwp_#{k}"] = v
|
256
|
+
end
|
257
|
+
end
|
175
258
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
259
|
+
params[:image_id] = image_id
|
260
|
+
instance = nil
|
261
|
+
|
262
|
+
request(:post, entry_points[:instances], {}, params) do |response|
|
263
|
+
c = Kernel.define_class("Instance")
|
264
|
+
instance = base_object(c, :instance, response)
|
265
|
+
yield instance if block_given?
|
183
266
|
end
|
267
|
+
|
268
|
+
return instance
|
184
269
|
end
|
185
|
-
realms
|
186
|
-
end
|
187
270
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
271
|
+
# Basic request method
|
272
|
+
#
|
273
|
+
def request(*args, &block)
|
274
|
+
conf = {
|
275
|
+
:method => (args[0] || 'get').to_sym,
|
276
|
+
:path => (args[1]=~/^http/) ? args[1] : "#{api_uri.to_s}#{args[1]}",
|
277
|
+
:query_args => args[2] || {},
|
278
|
+
:form_data => args[3] || {}
|
279
|
+
}
|
280
|
+
if conf[:query_args] != {}
|
281
|
+
conf[:path] += '?' + URI.escape(conf[:query_args].collect{ |key, value| "#{key}=#{value}" }.join('&')).to_s
|
282
|
+
end
|
283
|
+
logger << "[#{conf[:method].to_s.upcase}] #{conf[:path]}\n"
|
284
|
+
if conf[:method].eql?(:post)
|
285
|
+
RestClient.send(:post, conf[:path], conf[:form_data], default_headers) do |response|
|
286
|
+
yield response.body if block_given?
|
287
|
+
end
|
288
|
+
else
|
289
|
+
RestClient.send(conf[:method], conf[:path], default_headers) do |response|
|
290
|
+
yield response.body if block_given?
|
291
|
+
end
|
194
292
|
end
|
195
293
|
end
|
196
|
-
nil
|
197
|
-
end
|
198
294
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
end
|
295
|
+
# Check if specified collection have wanted feature
|
296
|
+
def feature?(collection, name)
|
297
|
+
@feature.has_key?(collection) && @feature[collection].include?(name)
|
298
|
+
end
|
204
299
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
300
|
+
# List available instance states and transitions between them
|
301
|
+
def instance_states
|
302
|
+
states = []
|
303
|
+
request(:get, entry_points[:instance_states]) do |response|
|
304
|
+
Nokogiri::XML(response).xpath('states/state').each do |state_el|
|
305
|
+
state = DeltaCloud::InstanceState::State.new(state_el['name'])
|
306
|
+
state_el.xpath('transition').each do |transition_el|
|
307
|
+
state.transitions << DeltaCloud::InstanceState::Transition.new(
|
308
|
+
transition_el['to'],
|
309
|
+
transition_el['action']
|
310
|
+
)
|
311
|
+
end
|
312
|
+
states << state
|
313
|
+
end
|
213
314
|
end
|
315
|
+
states
|
214
316
|
end
|
215
|
-
images
|
216
|
-
end
|
217
317
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
doc.get_elements( 'image' ).each do |instance|
|
222
|
-
uri = instance.attributes['href']
|
223
|
-
return DCloud::Image.new( self, uri, instance )
|
224
|
-
end
|
318
|
+
# Select instance state specified by name
|
319
|
+
def instance_state(name)
|
320
|
+
instance_states.select { |s| s.name.to_s.eql?(name.to_s) }.first
|
225
321
|
end
|
226
|
-
nil
|
227
|
-
end
|
228
322
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
doc = REXML::Document.new( response )
|
233
|
-
doc.get_elements( 'instances/instance' ).each do |instance|
|
234
|
-
uri = instance.attributes['href']
|
235
|
-
instances << DCloud::Instance.new( self, uri, instance )
|
236
|
-
end
|
323
|
+
# Skip parsing /api when we already got entry points
|
324
|
+
def discovered?
|
325
|
+
true if @entry_points!={}
|
237
326
|
end
|
238
|
-
instances
|
239
|
-
end
|
240
327
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
328
|
+
def documentation(collection, operation=nil)
|
329
|
+
data = {}
|
330
|
+
request(:get, "/docs/#{collection}") do |body|
|
331
|
+
document = Nokogiri::XML(body)
|
332
|
+
if operation
|
333
|
+
data[:description] = document.xpath('/docs/collection/operations/operation[@name = "'+operation+'"]/description').first
|
334
|
+
return false unless data[:description]
|
335
|
+
data[:params] = []
|
336
|
+
(document/"/docs/collection/operations/operation[@name='#{operation}']/parameter").each do |param|
|
337
|
+
data[:params] << {
|
338
|
+
:name => param['name'],
|
339
|
+
:required => param['type'] == 'optional',
|
340
|
+
:type => (param/'class').text
|
341
|
+
}
|
342
|
+
end
|
343
|
+
else
|
344
|
+
data[:description] = (document/'/docs/collection/description').text
|
345
|
+
end
|
247
346
|
end
|
347
|
+
return Documentation.new(data)
|
248
348
|
end
|
249
|
-
nil
|
250
|
-
end
|
251
349
|
|
252
|
-
|
253
|
-
|
254
|
-
|
350
|
+
private
|
351
|
+
|
352
|
+
def default_headers
|
353
|
+
{
|
354
|
+
:authorization => "Basic "+Base64.encode64("#{@username}:#{@password}"),
|
355
|
+
:accept => "application/xml"
|
356
|
+
}
|
357
|
+
end
|
358
|
+
|
359
|
+
def add_class_record(obj)
|
360
|
+
return if self.classes.include?(obj.class)
|
361
|
+
self.classes << obj.class
|
255
362
|
end
|
256
|
-
return false
|
257
|
-
end
|
258
363
|
|
259
|
-
def fetch_instance(uri)
|
260
|
-
xml = fetch_resource( :instance, uri )
|
261
|
-
return DCloud::Instance.new( self, uri, xml ) if xml
|
262
|
-
nil
|
263
364
|
end
|
264
365
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
params[:"hwp_#{k}"] = v
|
366
|
+
class Documentation
|
367
|
+
attr_reader :description
|
368
|
+
attr_reader :params
|
369
|
+
|
370
|
+
def initialize(opts={})
|
371
|
+
@description = opts[:description]
|
372
|
+
@params = parse_parameters(opts[:params]) if opts[:params]
|
373
|
+
self
|
374
|
+
end
|
375
|
+
|
376
|
+
class OperationParameter
|
377
|
+
attr_reader :name
|
378
|
+
attr_reader :type
|
379
|
+
attr_reader :required
|
380
|
+
attr_reader :description
|
381
|
+
|
382
|
+
def initialize(data)
|
383
|
+
@name, @type, @required, @description = data[:name], data[:type], data[:required], data[:description]
|
384
|
+
end
|
385
|
+
|
386
|
+
def to_comment
|
387
|
+
" # @param [#{@type}, #{@name}] #{@description}"
|
288
388
|
end
|
389
|
+
|
289
390
|
end
|
290
391
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
uri = instance.attributes['href']
|
296
|
-
return DCloud::Instance.new( self, uri, instance )
|
392
|
+
private
|
393
|
+
|
394
|
+
def parse_parameters(params)
|
395
|
+
params.collect { |p| OperationParameter.new(p) }
|
297
396
|
end
|
397
|
+
|
298
398
|
end
|
299
399
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
400
|
+
module InstanceState
|
401
|
+
|
402
|
+
class State
|
403
|
+
attr_reader :name
|
404
|
+
attr_reader :transitions
|
405
|
+
|
406
|
+
def initialize(name)
|
407
|
+
@name, @transitions = name, []
|
307
408
|
end
|
308
409
|
end
|
309
|
-
storage_volumes
|
310
|
-
end
|
311
410
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
411
|
+
class Transition
|
412
|
+
attr_reader :to
|
413
|
+
attr_reader :action
|
414
|
+
|
415
|
+
def initialize(to, action)
|
416
|
+
@to = to
|
417
|
+
@action = action
|
418
|
+
end
|
419
|
+
|
420
|
+
def auto?
|
421
|
+
@action.nil?
|
318
422
|
end
|
319
423
|
end
|
320
|
-
nil
|
321
424
|
end
|
322
425
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
end
|
426
|
+
module HWP
|
427
|
+
|
428
|
+
class Property
|
429
|
+
attr_reader :name, :unit, :value, :kind
|
328
430
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
doc.get_elements( 'storage-snapshots/storage-snapshot' ).each do |instance|
|
334
|
-
uri = instance.attributes['href']
|
335
|
-
storage_snapshots << DCloud::StorageSnapshot.new( self, uri, instance )
|
431
|
+
def initialize(xml, name)
|
432
|
+
@kind, @value, @unit = xml['kind'].to_sym, xml['value'], xml['unit']
|
433
|
+
declare_ranges(xml)
|
434
|
+
self
|
336
435
|
end
|
436
|
+
|
437
|
+
def present?
|
438
|
+
! @value.nil?
|
439
|
+
end
|
440
|
+
|
441
|
+
private
|
442
|
+
|
443
|
+
def declare_ranges(xml)
|
444
|
+
case xml['kind']
|
445
|
+
when 'range':
|
446
|
+
self.class.instance_eval do
|
447
|
+
attr_reader :range
|
448
|
+
end
|
449
|
+
@range = { :from => xml.xpath('range').first['first'], :to => xml.xpath('range').first['last'] }
|
450
|
+
when 'enum':
|
451
|
+
self.class.instance_eval do
|
452
|
+
attr_reader :options
|
453
|
+
end
|
454
|
+
@options = xml.xpath('enum/entry').collect { |e| e['value'] }
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
337
458
|
end
|
338
|
-
storage_snapshots
|
339
|
-
end
|
340
459
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
return DCloud::StorageSnapshot.new( self, uri, storage_snapshot )
|
460
|
+
# FloatProperty is like Property but return value is Float instead of String.
|
461
|
+
class FloatProperty < Property
|
462
|
+
def initialize(xml, name)
|
463
|
+
super(xml, name)
|
464
|
+
@value = @value.to_f if @value
|
347
465
|
end
|
348
466
|
end
|
349
|
-
nil
|
350
467
|
end
|
351
468
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
469
|
+
end
|
470
|
+
|
471
|
+
class String
|
472
|
+
|
473
|
+
# Create a class name from string
|
474
|
+
def classify
|
475
|
+
self.singularize.camelize
|
356
476
|
end
|
357
477
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
nil
|
478
|
+
# Camelize converts strings to UpperCamelCase
|
479
|
+
def camelize
|
480
|
+
self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
362
481
|
end
|
363
482
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
def discover_entry_points
|
369
|
-
return if @discovered
|
370
|
-
request(api_uri.to_s) do |response|
|
371
|
-
doc = REXML::Document.new( response )
|
372
|
-
@driver_name = doc.root.attributes['driver']
|
373
|
-
doc.get_elements( 'api/link' ).each do |link|
|
374
|
-
rel = link.attributes['rel'].to_sym
|
375
|
-
uri = link.attributes['href']
|
376
|
-
@entry_points[rel] = uri
|
377
|
-
@features[rel] ||= []
|
378
|
-
link.get_elements('feature').each do |feature|
|
379
|
-
@features[rel] << feature.attributes['name'].to_sym
|
380
|
-
end
|
381
|
-
end
|
382
|
-
end
|
383
|
-
@discovered = true
|
483
|
+
# Strip 's' character from end of string
|
484
|
+
def singularize
|
485
|
+
self.gsub(/s$/, '')
|
384
486
|
end
|
385
487
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
if method.eql?(:get)
|
406
|
-
RestClient.send(method, request_path, headers) do |response|
|
407
|
-
@last_request_xml = response
|
408
|
-
yield response.to_s
|
409
|
-
end
|
410
|
-
else
|
411
|
-
RestClient.send(method, request_path, form_data, headers) do |response|
|
412
|
-
@last_request_xml = response
|
413
|
-
yield response.to_s
|
414
|
-
end
|
415
|
-
end
|
488
|
+
# Convert string to float if string value seems like Float
|
489
|
+
def convert
|
490
|
+
return self.to_f if self.strip =~ /^([\d\.]+$)/
|
491
|
+
self
|
492
|
+
end
|
493
|
+
|
494
|
+
# Simply converts whitespaces and - symbols to '_' which is safe for Ruby
|
495
|
+
def sanitize
|
496
|
+
self.gsub(/(\W+)/, '_')
|
497
|
+
end
|
498
|
+
|
499
|
+
end
|
500
|
+
|
501
|
+
module Kernel
|
502
|
+
|
503
|
+
# Get defined class or declare a new one, when class was not declared before
|
504
|
+
def define_class(name)
|
505
|
+
DeltaCloud.const_get(name) rescue DeltaCloud.const_set(name, Class.new)
|
416
506
|
end
|
417
507
|
|
418
508
|
end
|