deltacloud-client 0.0.9.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,282 @@
1
+ #
2
+ # Copyright (C) 2010 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 'string'
20
+
21
+ module DeltaCloud
22
+
23
+ class BaseObjectParamError < Exception; end
24
+ class NoHandlerForMethod < Exception; end
25
+
26
+ # BaseObject model basically provide the basic operation around
27
+ # REST model, like defining a links between different objects,
28
+ # element with text values, or collection of these elements
29
+ class BaseObject
30
+ attr_reader :id, :url, :client, :base_name
31
+ attr_reader :objects
32
+
33
+ alias :uri :url
34
+
35
+ # For initializing new object you require to set
36
+ # id, url, client and name attribute.
37
+ def initialize(opts={}, &block)
38
+ @id, @url, @client, @base_name = opts[:id], opts[:url], opts[:client], opts[:name]
39
+ @objects = []
40
+ raise BaseObjectParamError if @id.nil? or @url.nil? or @client.nil? or @base_name.nil?
41
+ yield self if block_given?
42
+ end
43
+
44
+ # This method add link to another object in REST model
45
+ # XML syntax: <link rel="destroy" href="http://localhost/api/resource" method="post"/>
46
+ def add_link!(object_name, id)
47
+ @objects << {
48
+ :type => :link,
49
+ :method_name => object_name.sanitize,
50
+ :id => id
51
+ }
52
+ @objects << {
53
+ :type => :text,
54
+ :method_name => "#{object_name.sanitize}_id",
55
+ :value => id
56
+ }
57
+ end
58
+
59
+ # Method add property for hardware profile
60
+ def add_hwp_property!(name, property, type)
61
+ hwp_property=case type
62
+ when :float then DeltaCloud::HWP::FloatProperty.new(property, name)
63
+ when :integer then DeltaCloud::HWP::Property.new(property, name)
64
+ end
65
+ @objects << {
66
+ :type => :property,
67
+ :method_name => name.sanitize,
68
+ :property => hwp_property
69
+ }
70
+ end
71
+
72
+ # This method define text object in REST model
73
+ # XML syntax: <name>Instance 1</name>
74
+ def add_text!(object_name, value)
75
+ @objects << {
76
+ :type => :text,
77
+ :method_name => object_name.sanitize,
78
+ :value => value
79
+ }
80
+ end
81
+
82
+ # This method define collection of text elements inside REST model
83
+ # XML syntax: <addresses>
84
+ # <address>127.0.0.1</address>
85
+ # <address>127.0.0.2</address>
86
+ # </addresses>
87
+ def add_collection!(collection_name, values=[])
88
+ @objects << {
89
+ :type => :collection,
90
+ :method_name => collection_name.sanitize,
91
+ :values => values
92
+ }
93
+ end
94
+
95
+ # Basic method hander. This define a way how value from property
96
+ # will be returned
97
+ def method_handler(m, args=[])
98
+ case m[:type]
99
+ when :link then return @client.send(m[:method_name].singularize, m[:id])
100
+ when :text then return m[:value]
101
+ when :property then return m[:property]
102
+ when :collection then return m[:values]
103
+ else raise NoHandlerForMethod
104
+ end
105
+ end
106
+
107
+ def method_missing(method_name, *args)
108
+ # First of all search throught array for method name
109
+ m = search_for_method(method_name)
110
+ if m.nil?
111
+ warn "[WARNING] Method unsupported by API: '#{self.class}.#{method_name}(#{args.inspect})'"
112
+ return nil
113
+ else
114
+ # Call appropriate handler for method
115
+ method_handler(m, args)
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def search_for_method(name)
122
+ @objects.detect { |o| o[:method_name] == "#{name}" }
123
+ end
124
+
125
+ end
126
+
127
+ class ActionObject < BaseObject
128
+
129
+ def initialize(opts={}, &block)
130
+ super(opts)
131
+ @action_urls = opts[:action_urls] || []
132
+ @actions = []
133
+ end
134
+
135
+ # This trigger is called right after action.
136
+ # This method does nothing inside ActionObject
137
+ # but it can be redifined and used in meta-programming
138
+ def action_trigger(action)
139
+ end
140
+
141
+ def add_action_link!(id, link)
142
+ m = {
143
+ :type => :action_link,
144
+ :method_name => "#{link['rel'].sanitize}!",
145
+ :id => id,
146
+ :href => link['href'],
147
+ :rel => link['rel'].sanitize,
148
+ :method => link['method'].sanitize
149
+ }
150
+ @objects << m
151
+ @actions << [m[:rel], m[:href]]
152
+ @action_urls << m[:href]
153
+ end
154
+
155
+ def actions
156
+ @objects.inject([]) do |result, item|
157
+ result << [item[:rel], item[:href]] if item[:type].eql?(:action_link)
158
+ result
159
+ end
160
+ end
161
+
162
+ def action_urls
163
+ actions.collect { |a| a.last }
164
+ end
165
+
166
+ alias :base_method_handler :method_handler
167
+
168
+ # First call BaseObject method handler,
169
+ # then, if not method found try ActionObject handler
170
+ def method_handler(m, args=[])
171
+ begin
172
+ base_method_handler(m, args)
173
+ rescue NoHandlerForMethod
174
+ case m[:type]
175
+ when :action_link then do_action(m)
176
+ else raise NoHandlerForMethod
177
+ end
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def do_action(m)
184
+ @client.request(:"#{m[:method]}", m[:href], {}, {})
185
+ action_trigger(m[:rel])
186
+ end
187
+
188
+ end
189
+
190
+ class StateFullObject < ActionObject
191
+ attr_reader :state
192
+
193
+ def initialize(opts={}, &block)
194
+ super(opts)
195
+ @state = opts[:initial_state] || ''
196
+ add_default_states!
197
+ end
198
+
199
+ def add_default_states!
200
+ @objects << {
201
+ :method_name => 'stopped?',
202
+ :type => :state,
203
+ :state => 'STOPPED'
204
+ }
205
+ @objects << {
206
+ :method_name => 'running?',
207
+ :type => :state,
208
+ :state => 'RUNNING'
209
+ }
210
+ @objects << {
211
+ :method_name => 'pending?',
212
+ :type => :state,
213
+ :state => 'PENDING'
214
+ }
215
+ @objects << {
216
+ :method_name => 'shutting_down?',
217
+ :type => :state,
218
+ :state => 'SHUTTING_DOWN'
219
+ }
220
+ end
221
+
222
+ def action_trigger(action)
223
+ # Refresh object state after action
224
+ @new_state_object = @client.send(self.base_name, self.id)
225
+ @state = @new_state_object.state
226
+ self.update_actions!
227
+ end
228
+
229
+ alias :action_method_handler :method_handler
230
+
231
+ def method_handler(m, args=[])
232
+ begin
233
+ action_method_handler(m, args)
234
+ rescue NoHandlerForMethod
235
+ case m[:type]
236
+ when :state then evaluate_state(m[:state], @state)
237
+ else raise NoHandlerForMethod
238
+ end
239
+ end
240
+ end
241
+
242
+ # private
243
+
244
+ def evaluate_state(method_state, current_state)
245
+ method_state.eql?(current_state)
246
+ end
247
+
248
+ def action_objects
249
+ @objects.select { |o| o[:type] == :action_link }
250
+ end
251
+
252
+ def update_actions!
253
+ new_actions = @new_state_object.action_objects
254
+ @objects.reject! { |o| o[:type] == :action_link }
255
+ @objects = (@objects + new_actions)
256
+ end
257
+
258
+ end
259
+
260
+ def self.add_class(name, parent=:base)
261
+ parent_class = case parent
262
+ when :base then 'BaseObject'
263
+ when :action then 'ActionObject'
264
+ when :state then 'StateFullObject'
265
+ end
266
+ @defined_classes ||= []
267
+ if @defined_classes.include?(name)
268
+ DeltaCloud::API.class_eval("#{name.classify}")
269
+ else
270
+ DeltaCloud::API.class_eval("class #{name.classify} < DeltaCloud::#{parent_class}; end")
271
+ DeltaCloud::API.const_get("#{name.classify}")
272
+ end
273
+ end
274
+
275
+ def self.guess_model_type(response)
276
+ response = Nokogiri::XML(response.to_s)
277
+ return :action if ((response/'//actions').length == 1) and ((response/'//state').length == 0)
278
+ return :state if ((response/'//actions').length == 1) and ((response/'//state').length == 1)
279
+ return :base
280
+ end
281
+
282
+ end
data/lib/deltacloud.rb CHANGED
@@ -21,6 +21,11 @@ require 'rest_client'
21
21
  require 'base64'
22
22
  require 'logger'
23
23
 
24
+ require 'hwp_properties'
25
+ require 'instance_state'
26
+ require 'documentation'
27
+ require 'base_object'
28
+
24
29
  module DeltaCloud
25
30
 
26
31
  # Get a new API client instance
@@ -56,27 +61,11 @@ module DeltaCloud
56
61
  API.new(nil, nil, url).driver_name
57
62
  end
58
63
 
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
64
  class API
74
- attr_accessor :logger
75
65
  attr_reader :api_uri, :driver_name, :api_version, :features, :entry_points
76
66
 
77
67
  def initialize(user_name, password, api_url, opts={}, &block)
78
68
  opts[:version] = true
79
- @logger = opts[:verbose] ? Logger.new(STDERR) : []
80
69
  @username, @password = user_name, password
81
70
  @api_uri = URI.parse(api_url)
82
71
  @features, @entry_points = {}, {}
@@ -101,147 +90,91 @@ module DeltaCloud
101
90
  # Define methods based on 'rel' attribute in entry point
102
91
  # Two methods are declared: 'images' and 'image'
103
92
  def declare_entry_points_methods(entry_points)
104
- logger = @logger
93
+
105
94
  API.instance_eval do
106
95
  entry_points.keys.select {|k| [:instance_states].include?(k)==false }.each do |model|
96
+
107
97
  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)
98
+ request(:get, entry_points[model], args.first) do |response|
99
+ base_object_collection(model, response)
113
100
  end
114
101
  end
115
- logger << "[API] Added method #{model}\n"
102
+
116
103
  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)
104
+ request(:get, "#{entry_points[model]}/#{args[0]}") do |response|
105
+ base_object(model, response)
122
106
  end
123
107
  end
124
- logger << "[API] Added method #{model.to_s.singularize}\n"
108
+
125
109
  define_method :"fetch_#{model.to_s.singularize}" do |url|
126
110
  id = url.grep(/\/#{model}\/(.*)$/)
127
111
  self.send(model.to_s.singularize.to_sym, $1)
128
112
  end
113
+
129
114
  end
130
115
  end
131
116
  end
132
117
 
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)
118
+ def base_object_collection(model, response)
119
+ Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").collect do |item|
120
+ base_object(model, item.to_s)
141
121
  end
142
- return collection
143
122
  end
144
123
 
145
124
  # 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
-
153
-
154
- end
155
- obj = xml_to_class(c, item)
156
- end
157
- return obj
125
+ def base_object(model, response)
126
+ c = DeltaCloud.add_class("#{model}", DeltaCloud::guess_model_type(response))
127
+ xml_to_class(c, Nokogiri::XML(response).xpath("#{model.to_s.singularize}").first)
158
128
  end
159
129
 
160
130
  # Convert XML response to defined Ruby Class
161
- def xml_to_class(c, item)
162
- obj = c.new
163
- # Set default attributes
164
- obj.id = item['id']
165
- api = self
166
- c.instance_eval do
167
- define_method :method_missing do |method|
168
- warn "[WARNING] Method '#{method}' is not available for this resource (#{c.name})."
169
- return nil
170
- end
171
- define_method :client do
172
- api
131
+ def xml_to_class(base_object, item)
132
+
133
+ return nil unless item
134
+
135
+ params = {
136
+ :id => item['id'],
137
+ :url => item['href'],
138
+ :name => item.name,
139
+ :client => self
140
+ }
141
+ params.merge!({ :initial_state => (item/'state').text.sanitize }) if (item/'state').length > 0
142
+
143
+ obj = base_object.new(params)
144
+
145
+ # Traverse across XML document and deal with elements
146
+ item.xpath('./*').each do |attribute|
147
+
148
+ # Do a link for elements which are links to other REST models
149
+ if self.entry_points.keys.include?(:"#{attribute.name}s")
150
+ obj.add_link!(attribute.name, attribute['id']) && next
173
151
  end
174
- end
175
- obj.uri = item['href']
176
- logger = @logger
177
- logger << "[DC] Creating class #{obj.class.name}\n"
178
- obj.instance_eval do
179
- # Declare methods for all attributes in object
180
- item.xpath('./*').each do |attribute|
181
- # If attribute is a link to another object then
182
- # create a method which request this object from API
183
- if api.entry_points.keys.include?(:"#{attribute.name}s")
184
- c.instance_eval do
185
- define_method :"#{attribute.name.sanitize}" do
186
- client.send(:"#{attribute.name}", attribute['id'] )
187
- end
188
- logger << "[DC] Added #{attribute.name} to class #{obj.class.name}\n"
189
- end
152
+
153
+ # Do a HWP property for hardware profile properties
154
+ if attribute.name == 'property'
155
+ if attribute['value'] =~ /^(\d+)$/
156
+ obj.add_hwp_property!(attribute['name'], attribute, :float) && next
190
157
  else
191
- # Define methods for other attributes
192
- c.instance_eval do
193
- case attribute.name
194
- # When response cointains 'link' block, declare
195
- # methods to call links inside. This is used for instance
196
- # to dynamicaly create .stop!, .start! methods
197
- when "actions":
198
- actions = []
199
- attribute.xpath('link').each do |link|
200
- actions << [link['rel'], link[:href]]
201
- define_method :"#{link['rel'].sanitize}!" do
202
- client.request(:"#{link['method']}", link['href'], {}, {})
203
- @current_state = client.send(:"#{item.name}", item['id']).state
204
- obj.instance_eval do |o|
205
- def state
206
- @current_state
207
- end
208
- end
209
- end
210
- end
211
- define_method :actions do
212
- actions.collect { |a| a.first }
213
- end
214
- define_method :actions_urls do
215
- urls = {}
216
- actions.each { |a| urls[a.first] = a.last }
217
- urls
218
- end
219
- # Property attribute is handled differently
220
- when "property":
221
- attr_accessor :"#{attribute['name'].sanitize}"
222
- if attribute['value'] =~ /^(\d+)$/
223
- obj.send(:"#{attribute['name'].sanitize}=",
224
- DeltaCloud::HWP::FloatProperty.new(attribute, attribute['name']))
225
- else
226
- obj.send(:"#{attribute['name'].sanitize}=",
227
- DeltaCloud::HWP::Property.new(attribute, attribute['name']))
228
- end
229
- # Public and private addresses are returned as Array
230
- when "public_addresses", "private_addresses":
231
- attr_accessor :"#{attribute.name.sanitize}"
232
- obj.send(:"#{attribute.name.sanitize}=",
233
- attribute.xpath('address').collect { |address| address.text })
234
- # Value for other attributes are just returned using
235
- # method with same name as attribute (eg. .owner_id, .state)
236
- else
237
- attr_accessor :"#{attribute.name.sanitize}"
238
- obj.send(:"#{attribute.name.sanitize}=", attribute.text.convert)
239
- logger << "[DC] Added method #{attribute.name}[#{attribute.text}] to #{obj.class.name}\n"
240
- end
241
- end
158
+ obj.add_hwp_property!(attribute['name'], attribute, :integer) && next
242
159
  end
243
160
  end
161
+
162
+ # If there are actions, add they to ActionObject/StateFullObject
163
+ if attribute.name == 'actions'
164
+ (attribute/'link').each do |link|
165
+ obj.add_action_link!(item['id'], link)
166
+ end && next
167
+ end
168
+
169
+ # Deal with collections like public-addresses, private-addresses
170
+ if (attribute/'./*').length > 0
171
+ obj.add_collection!(attribute.name, (attribute/'*').collect { |value| value.text }) && next
172
+ end
173
+
174
+ # Anything else is treaten as text object
175
+ obj.add_text!(attribute.name, attribute.text.convert)
244
176
  end
177
+
245
178
  return obj
246
179
  end
247
180
 
@@ -252,73 +185,54 @@ module DeltaCloud
252
185
  api_xml = Nokogiri::XML(response)
253
186
  @driver_name = api_xml.xpath('/api').first['driver']
254
187
  @api_version = api_xml.xpath('/api').first['version']
255
- logger << "[API] Version #{@api_version}\n"
256
- logger << "[API] Driver #{@driver_name}\n"
188
+
257
189
  api_xml.css("api > link").each do |entry_point|
258
190
  rel, href = entry_point['rel'].to_sym, entry_point['href']
259
191
  @entry_points.store(rel, href)
260
- logger << "[API] Entry point '#{rel}' added\n"
192
+
261
193
  entry_point.css("feature").each do |feature|
262
194
  @features[rel] ||= []
263
195
  @features[rel] << feature['name'].to_sym
264
- logger << "[API] Feature #{feature['name']} added to #{rel}\n"
196
+
265
197
  end
266
198
  end
267
199
  end
268
200
  declare_entry_points_methods(@entry_points)
269
201
  end
270
202
 
271
- def create_key(opts={}, &block)
272
- params = { :name => opts[:name] }
273
- key = nil
274
- request(:post, entry_points[:keys], {}, params) do |response|
275
- c = DeltaCloud.define_class("Key")
276
- key = base_object(c, :key, response)
277
- yield key if block_given?
278
- end
279
- return key
280
- end
281
-
282
- # Create a new instance, using image +image_id+. Possible optiosn are
203
+ # Generate create_* methods dynamically
283
204
  #
284
- # name - a user-defined name for the instance
285
- # realm - a specific realm for placement of the instance
286
- # hardware_profile - either a string giving the name of the
287
- # hardware profile or a hash. The hash must have an
288
- # entry +id+, giving the id of the hardware profile,
289
- # and may contain additional names of properties,
290
- # e.g. 'storage', to override entries in the
291
- # hardware profile
292
- def create_instance(image_id, opts={}, &block)
293
- name = opts[:name]
294
- realm_id = opts[:realm]
295
- user_data = opts[:user_data]
296
- key_name = opts[:key_name]
297
-
298
- params = {}
299
- ( params[:realm_id] = realm_id ) if realm_id
300
- ( params[:name] = name ) if name
301
- ( params[:user_data] = user_data ) if user_data
302
- ( params[:keyname] = key_name ) if key_name
303
-
304
- if opts[:hardware_profile].is_a?(String)
305
- params[:hwp_id] = opts[:hardware_profile]
306
- elsif opts[:hardware_profile].is_a?(Hash)
307
- opts[:hardware_profile].each do |k,v|
308
- params[:"hwp_#{k}"] = v
205
+ def method_missing(name, *args)
206
+ if name.to_s =~ /create_(\w+)/
207
+ params = args[0] if args[0] and args[0].class.eql?(Hash)
208
+ params ||= args[1] if args[1] and args[1].class.eql?(Hash)
209
+ params ||= {}
210
+
211
+ # FIXME: This fixes are related to Instance model and should be
212
+ # replaced by 'native' parameter names
213
+
214
+ params[:realm_id] ||= params[:realm] if params[:realm]
215
+ params[:keyname] ||= params[:key_name] if params[:key_name]
216
+
217
+ if params[:hardware_profile] and params[:hardware_profile].class.eql?(Hash)
218
+ params[:hardware_profile].each do |k,v|
219
+ params[:"hwp_#{k}"] ||= v
220
+ end
221
+ else
222
+ params[:hwp_id] ||= params[:hardware_profile]
309
223
  end
310
- end
311
224
 
312
- params[:image_id] = image_id
313
- instance = nil
225
+ params[:image_id] ||= params[:image_id] || args[0] if args[0].class!=Hash
314
226
 
315
- request(:post, entry_points[:instances], {}, params) do |response|
316
- c = DeltaCloud.define_class("Instance")
317
- instance = base_object(c, :instance, response)
318
- yield instance if block_given?
319
- end
227
+ obj = nil
320
228
 
321
- return instance
229
+ request(:post, entry_points[:"#{$1}s"], {}, params) do |response|
230
+ obj = base_object(:"#{$1}", response)
231
+ yield obj if block_given?
232
+ end
233
+ return obj
234
+ end
235
+ raise NoMethodError
322
236
  end
323
237
 
324
238
  # Basic request method
@@ -333,9 +247,10 @@ module DeltaCloud
333
247
  if conf[:query_args] != {}
334
248
  conf[:path] += '?' + URI.escape(conf[:query_args].collect{ |key, value| "#{key}=#{value}" }.join('&')).to_s
335
249
  end
336
- logger << "[#{conf[:method].to_s.upcase}] #{conf[:path]}\n"
250
+
337
251
  if conf[:method].eql?(:post)
338
252
  RestClient.send(:post, conf[:path], conf[:form_data], default_headers) do |response, request, block|
253
+ handle_backend_error(response) if response.code.eql?(500)
339
254
  if response.respond_to?('body')
340
255
  yield response.body if block_given?
341
256
  else
@@ -344,15 +259,41 @@ module DeltaCloud
344
259
  end
345
260
  else
346
261
  RestClient.send(conf[:method], conf[:path], default_headers) do |response, request, block|
347
- if response.respond_to?('body')
348
- yield response.body if block_given?
262
+ handle_backend_error(response) if response.code.eql?(500)
263
+ if conf[:method].eql?(:get) and [301, 302, 307].include? response.code
264
+ response.follow_redirection(request) do |response, request, block|
265
+ if response.respond_to?('body')
266
+ yield response.body if block_given?
267
+ else
268
+ yield response.to_s if block_given?
269
+ end
270
+ end
349
271
  else
350
- yield response.to_s if block_given?
272
+ if response.respond_to?('body')
273
+ yield response.body if block_given?
274
+ else
275
+ yield response.to_s if block_given?
276
+ end
351
277
  end
352
278
  end
353
279
  end
354
280
  end
355
281
 
282
+ # Re-raise backend errors as on exception in client with message from
283
+ # backend
284
+ class BackendError < Exception
285
+ def initialize(opts={})
286
+ @message = opts[:message]
287
+ end
288
+ def message
289
+ @message
290
+ end
291
+ end
292
+
293
+ def handle_backend_error(response)
294
+ raise BackendError.new(:message => (Nokogiri::XML(response)/'error/message').text)
295
+ end
296
+
356
297
  # Check if specified collection have wanted feature
357
298
  def feature?(collection, name)
358
299
  @features.has_key?(collection) && @features[collection].include?(name)
@@ -386,6 +327,7 @@ module DeltaCloud
386
327
  true if @entry_points!={}
387
328
  end
388
329
 
330
+ # This method will retrieve API documentation for given collection
389
331
  def documentation(collection, operation=nil)
390
332
  data = {}
391
333
  request(:get, "/docs/#{collection}") do |body|
@@ -426,151 +368,4 @@ module DeltaCloud
426
368
 
427
369
  end
428
370
 
429
- class Documentation
430
-
431
- attr_reader :api, :description, :params, :collection_operations
432
- attr_reader :collection, :operation
433
-
434
- def initialize(api, opts={})
435
- @description, @api = opts[:description], api
436
- @params = parse_parameters(opts[:params]) if opts[:params]
437
- @collection_operations = opts[:operations] if opts[:operations]
438
- @collection = opts[:collection]
439
- @operation = opts[:operation]
440
- self
441
- end
442
-
443
- def operations
444
- @collection_operations.collect { |o| api.documentation(@collection, o) }
445
- end
446
-
447
- class OperationParameter
448
- attr_reader :name
449
- attr_reader :type
450
- attr_reader :required
451
- attr_reader :description
452
-
453
- def initialize(data)
454
- @name, @type, @required, @description = data[:name], data[:type], data[:required], data[:description]
455
- end
456
-
457
- def to_comment
458
- " # @param [#{@type}, #{@name}] #{@description}"
459
- end
460
-
461
- end
462
-
463
- private
464
-
465
- def parse_parameters(params)
466
- params.collect { |p| OperationParameter.new(p) }
467
- end
468
-
469
- end
470
-
471
- module InstanceState
472
-
473
- class State
474
- attr_reader :name
475
- attr_reader :transitions
476
-
477
- def initialize(name)
478
- @name, @transitions = name, []
479
- end
480
- end
481
-
482
- class Transition
483
- attr_reader :to
484
- attr_reader :action
485
-
486
- def initialize(to, action)
487
- @to = to
488
- @action = action
489
- end
490
-
491
- def auto?
492
- @action.nil?
493
- end
494
- end
495
- end
496
-
497
- module HWP
498
-
499
- class Property
500
- attr_reader :name, :unit, :value, :kind
501
-
502
- def initialize(xml, name)
503
- @name, @kind, @value, @unit = xml['name'], xml['kind'].to_sym, xml['value'], xml['unit']
504
- declare_ranges(xml)
505
- self
506
- end
507
-
508
- def present?
509
- ! @value.nil?
510
- end
511
-
512
- private
513
-
514
- def declare_ranges(xml)
515
- case xml['kind']
516
- when 'range':
517
- self.class.instance_eval do
518
- attr_reader :range
519
- end
520
- @range = { :from => xml.xpath('range').first['first'], :to => xml.xpath('range').first['last'] }
521
- when 'enum':
522
- self.class.instance_eval do
523
- attr_reader :options
524
- end
525
- @options = xml.xpath('enum/entry').collect { |e| e['value'] }
526
- end
527
- end
528
-
529
- end
530
-
531
- # FloatProperty is like Property but return value is Float instead of String.
532
- class FloatProperty < Property
533
- def initialize(xml, name)
534
- super(xml, name)
535
- @value = @value.to_f if @value
536
- end
537
- end
538
- end
539
-
540
- end
541
-
542
- class String
543
-
544
- unless method_defined?(:classify)
545
- # Create a class name from string
546
- def classify
547
- self.singularize.camelize
548
- end
549
- end
550
-
551
- unless method_defined?(:camelize)
552
- # Camelize converts strings to UpperCamelCase
553
- def camelize
554
- self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
555
- end
556
- end
557
-
558
- unless method_defined?(:singularize)
559
- # Strip 's' character from end of string
560
- def singularize
561
- self.gsub(/s$/, '')
562
- end
563
- end
564
-
565
- # Convert string to float if string value seems like Float
566
- def convert
567
- return self.to_f if self.strip =~ /^([\d\.]+$)/
568
- self
569
- end
570
-
571
- # Simply converts whitespaces and - symbols to '_' which is safe for Ruby
572
- def sanitize
573
- self.gsub(/(\W+)/, '_')
574
- end
575
-
576
371
  end