# File lib/deltacloud.rb, line 72 def initialize(user_name, password, api_url, opts={}, &block) opts[:version] = true @api_driver, @api_provider = opts[:driver], opts[:provider] @username, @password = opts[:username] || user_name, opts[:password] || password @api_uri = URI.parse(api_url) @features, @entry_points = {}, {} @verbose = opts[:verbose] || false discover_entry_points if entry_points.include?(:buckets) extend(ClientBucketMethods) end yield self if block_given? end
Return API hostname
# File lib/deltacloud.rb, line 123 def api_host; @api_uri.host ; end
Return API path
# File lib/deltacloud.rb, line 129 def api_path; @api_uri.path ; end
Return API port
# File lib/deltacloud.rb, line 126 def api_port; @api_uri.port ; end
Add default attributes [id and href] to class
# File lib/deltacloud.rb, line 177 def base_object(model, response) c = DeltaCloud.add_class("#{model}", DeltaCloud::guess_model_type(response)) xml_to_class(c, Nokogiri::XML(response).xpath("#{model.to_s.singularize}").first) end
# File lib/deltacloud.rb, line 170 def base_object_collection(model, response) Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").collect do |item| base_object(model, item.to_s) end end
# File lib/deltacloud.rb, line 118 def connect(&block) yield self end
Define methods based on 'rel' attribute in entry point Two methods are declared: 'images' and 'image'
# File lib/deltacloud.rb, line 133 def declare_entry_points_methods(entry_points) API.instance_eval do entry_points.keys.select {|k| [:instance_states].include?(k)==false }.each do |model| define_method model do |*args| request(:get, entry_points[model], args.first) do |response| base_object_collection(model, response) end end define_method :"#{model.to_s.singularize}" do |*args| request(:get, "#{entry_points[model]}/#{args[0]}") do |response| base_object(model, response) end end define_method :"fetch_#{model.to_s.singularize}" do |url| url =~ %r\/#{model}\/(.*)$/ self.send(model.to_s.singularize.to_sym, $1) end end #define methods for blobs: if(entry_points.include?(:buckets)) define_method :"blob" do |*args| bucket = args[0]["bucket"] blob = args[0][:id] request(:get, "#{entry_points[:buckets]}/#{bucket}/#{blob}") do |response| base_object("blob", response) end end end end end
Get /api and parse entry points
# File lib/deltacloud.rb, line 266 def discover_entry_points return if discovered? request(:get, @api_uri.to_s) do |response| if response.code == 301 @api_uri = response.headers[:location] return discover_entry_points end api_xml = Nokogiri::XML(response) @driver_name = api_xml.xpath('/api').first[:driver] @api_version = api_xml.xpath('/api').first[:version] api_xml.css("api > link").each do |entry_point| rel, href = entry_point['rel'].to_sym, entry_point['href'] @entry_points.store(rel, href) entry_point.css("feature").each do |feature| @features[rel] ||= [] @features[rel] << feature['name'].to_sym end end end declare_entry_points_methods(@entry_points) end
Skip parsing /api when we already got entry points
# File lib/deltacloud.rb, line 439 def discovered? true if @entry_points!={} end
This method will retrieve API documentation for given collection
# File lib/deltacloud.rb, line 444 def documentation(collection, operation=nil) data = {} request(:get, "/docs/#{collection}") do |body| document = Nokogiri::XML(body) if operation data[:operation] = operation data[:description] = document.xpath('/docs/collection/operations/operation[@name = "'+operation+'"]/description').first.text.strip return false unless data[:description] data[:params] = [] (document/"/docs/collection/operations/operation[@name='#{operation}']/parameter").each do |param| data[:params] << { :name => param['name'], :required => param['type'] == 'optional', :type => (param/'class').text } end else data[:description] = (document/'/docs/collection/description').text data[:collection] = collection data[:operations] = (document/"/docs/collection/operations/operation").collect{ |o| o['name'] } end end return Documentation.new(self, data) end
# File lib/deltacloud.rb, line 350 def extended_headers headers = {} headers["X-Deltacloud-Driver"] = @api_driver.to_s if @api_driver headers["X-Deltacloud-Provider"] = @api_provider.to_s if @api_provider headers end
Check if specified collection have wanted feature
# File lib/deltacloud.rb, line 411 def feature?(collection, name) @features.has_key?(collection) && @features[collection].include?(name) end
Select instance state specified by name
# File lib/deltacloud.rb, line 434 def instance_state(name) instance_states.select { |s| s.name.to_s.eql?(name.to_s) }.first end
List available instance states and transitions between them
# File lib/deltacloud.rb, line 416 def instance_states states = [] request(:get, entry_points[:instance_states]) do |response| Nokogiri::XML(response).xpath('states/state').each do |state_el| state = DeltaCloud::InstanceState::State.new(state_el['name']) state_el.xpath('transition').each do |transition_el| state.transitions << DeltaCloud::InstanceState::Transition.new( transition_el['to'], transition_el['action'] ) end states << state end end states end
Generate create_* methods dynamically
# File lib/deltacloud.rb, line 293 def method_missing(name, *args) if name.to_s =~ %r^([\w_]+)_ids$/ return self.send(:"#{$1.pluralize}").map { |o| o.id } end if name.to_s =~ %r^create_(\w+)/ params = args[0] if args[0] and args[0].class.eql?(Hash) params ||= args[1] if args[1] and args[1].class.eql?(Hash) params ||= {} # FIXME: This fixes are related to Instance model and should be # replaced by 'native' parameter names params[:realm_id] ||= params[:realm] if params[:realm] params[:keyname] ||= params[:key_name] if params[:key_name] params[:user_data] = Base64::encode64(params[:user_data]) if params[:user_data] if params[:hardware_profile] and params[:hardware_profile].class.eql?(Hash) params[:hardware_profile].each do |k,v| params[:"hwp_#{k}"] ||= v end else params[:hwp_id] ||= params[:hardware_profile] end params[:image_id] ||= params[:image_id] || args[0] if args[0].class!=Hash obj = nil request(:post, entry_points[:"#{$1}s"], {}, params) do |response| obj = base_object(:"#{$1}", response) response_error(response) unless response_successful?(response.code) yield obj if block_given? end return obj end raise NoMethodError end
Basic request method
# File lib/deltacloud.rb, line 381 def request(*args, &block) conf = { :method => (args[0] || 'get').to_sym, :path => (args[1]=~%r^http/) ? args[1] : "#{api_uri.to_s}#{args[1]}", :query_args => args[2] || {}, :form_data => args[3] || {}, :timeout => args[4] || 45, :open_timeout => args[5] || 10 } if conf[:query_args] != {} conf[:path] += '?' + URI.escape(conf[:query_args].collect{ |key, value| "#{key}=#{value}" }.join('&')).to_s end if conf[:method].eql?(:post) resource = RestClient::Resource.new(conf[:path], :open_timeout => conf[:open_timeout], :timeout => conf[:timeout]) resource.send(:post, conf[:form_data], default_headers.merge(extended_headers)) do |response, request, block| response_error(response) unless response_successful? response.code yield response.to_s if block_given? end else resource = RestClient::Resource.new(conf[:path], :open_timeout => conf[:open_timeout], :timeout => conf[:timeout]) resource.send(conf[:method], default_headers.merge(extended_headers)) do |response, request, block| response_error(response) unless response_successful? response.code yield response.to_s if block_given? end end end
# File lib/deltacloud.rb, line 363 def response_error(response) xml = Nokogiri::XML(response.to_s) if (xml/'message').empty? and response.code.to_s =~ %r4(\d{2})/ DeltaCloud::HTTPError.client_error(response.code) else opts = { :driver => (xml/'backend').first[:driver], :provider => (xml/'backend').first[:provider], :params => (xml/'request/param').inject({}) { |r,p| r[:"#{p[:name]}"] = p.text; r } } backtrace = (xml/'backtrace').empty? ? nil : (xml/'backtrace').first.text.split("\n")[1..10].map { |l| l.strip } DeltaCloud::HTTPError.server_error(xml.root[:status] || response.code, (xml/'message').first.text, opts, backtrace) end end
# File lib/deltacloud.rb, line 357 def response_successful?(code) return true if code.to_s =~ %r^2(\d{2})$/ return true if code.to_s =~ %r^3(\d{2})$/ return false end
# File lib/deltacloud.rb, line 345 def use_config!(opts={}) @api_uri = URI.parse(opts[:url]) if opts[:url] use_driver(opts[:driver], opts) end
# File lib/deltacloud.rb, line 331 def use_driver(driver, opts={}) if driver @api_driver = driver @driver_name = driver @api_provider = opts[:provider] if opts[:provider] @features, @entry_points = {}, {} discover_entry_points end @username = opts[:username] if opts[:username] @password = opts[:password] if opts[:password] @api_provider = opts[:provider] if opts[:provider] return self end
This method can be used to switch back-end cloud for API instance using HTTP headers. Options must include: {
:driver => 'rhevm|ec2|gogrid|...', :username => 'API key for backend', :password => 'API secret key for backend',
} Optionally you can pass also :provider option to change provider entry-point
Example usage: client = Deltacloud::new('url', 'username', 'password') ... client.with_config(:driver => 'ec2', :username => ", :password => ") do |ec2|
ec2.realms
end
Note: After this block finish client instance will be set back to default state
@param [Hash, opts] New provider configuration
# File lib/deltacloud.rb, line 108 def with_config(opts, &block) api_instance = self.dup api_instance.use_driver(opts[:driver], :username => opts[:username], :password => opts[:password], :provider => opts[:provider]) yield api_instance if block_given? api_instance end
Convert XML response to defined Ruby Class
# File lib/deltacloud.rb, line 183 def xml_to_class(base_object, item) return nil unless item params = { :id => item['id'], :url => item['href'], :name => item.name, :client => self } params.merge!({ :initial_state => (item/'state').text.sanitize }) if (item/'state').length > 0 obj = base_object.new(params) # Traverse across XML document and deal with elements item.xpath('./*').each do |attribute| # Do a link for elements which are links to other REST models if self.entry_points.keys.include?(:"#{attribute.name}s") obj.add_link!(attribute.name, attribute['id']) && next unless (attribute.name == 'bucket' && item.name == 'blob') end # Do a HWP property for hardware profile properties if attribute.name == 'property' if attribute['value'] =~ %r^(\d+)\.(\d+)$/ obj.add_hwp_property!(attribute['name'], attribute, :float) && next else obj.add_hwp_property!(attribute['name'], attribute, :integer) && next end end # If there are actions, add they to ActionObject/StateFullObject if attribute.name == 'actions' (attribute/'link').each do |link| (obj.add_run_action!(item['id'], link) && next) if link[:rel] == 'run' obj.add_action_link!(item['id'], link) end && next end if attribute.name == 'mount' obj.add_link!("instance", (attribute/"./instance/@id").first.value) obj.add_text!("device", (attribute/"./device/@name").first.value) next end #deal with blob metadata if (attribute.name == 'user_metadata') meta = {} attribute.children.select {|x| x.name=="entry" }.each do |element| value = element.content.gsub!(%r(\n) +/,'') meta[element['key']] = value end obj.add_collection!(attribute.name, meta.inspect) && next end if (['public_addresses', 'private_addresses'].include? attribute.name) obj.add_addresses!(attribute.name, (attribute/'*')) && next end if ('authentication'.include? attribute.name) obj.add_authentication!(attribute[:type], (attribute/'*')) && next end #deal with providers if(attribute.name == 'provider') obj.add_provider!(attribute.attributes['id'].value, (attribute/'entrypoint')) && next end # Deal with collections like public-addresses, private-addresses if (attribute/'./*').length > 0 obj.add_collection!(attribute.name, (attribute/'*').collect { |value| value.text }) && next end #deal with blobs for buckets if(attribute.name == 'blob') obj.add_blob!(attribute.attributes['id'].value) && next end # Anything else is treaten as text object obj.add_text!(attribute.name, attribute.text.convert) end return obj end