You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@deltacloud.apache.org by mf...@apache.org on 2013/03/26 18:57:49 UTC
[03/30] git commit: Client: Complete rewrite of deltacloud-client
Client: Complete rewrite of deltacloud-client
- Now use Faraday HTTP lib
- Superb error reporting (based on Faraday middleware)
- Documentation
- Easy to read/fix bugs/add features
- Compatible with jRuby/MRI 2.0.0
Project: http://git-wip-us.apache.org/repos/asf/deltacloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/deltacloud/commit/b6d3365c
Tree: http://git-wip-us.apache.org/repos/asf/deltacloud/tree/b6d3365c
Diff: http://git-wip-us.apache.org/repos/asf/deltacloud/diff/b6d3365c
Branch: refs/heads/master
Commit: b6d3365c1e594a5a898d40d262db80882ceb963d
Parents: 95e8662
Author: Michal Fojtik <mf...@redhat.com>
Authored: Thu Mar 7 13:51:26 2013 +0100
Committer: Michal fojtik <mf...@redhat.com>
Committed: Tue Mar 26 15:21:34 2013 +0100
----------------------------------------------------------------------
client/.gitignore | 21 +-
client/Gemfile | 12 +
client/README | 127 ----
client/README.md | 73 +++
client/Rakefile | 92 +++-
client/deltacloud-client.gemspec | 13 +-
client/lib/base_object.rb | 386 ------------
client/lib/client_bucket_methods.rb | 69 --
client/lib/deltacloud.rb | 486 ---------------
client/lib/deltacloud/client.rb | 78 +++
client/lib/deltacloud/client/base_error.rb | 84 +++
client/lib/deltacloud/client/connection.rb | 135 ++++
.../lib/deltacloud/client/helpers/model_helper.rb | 69 ++
.../deltacloud/client/helpers/property_helper.rb | 103 +++
client/lib/deltacloud/client/helpers/xml_helper.rb | 33 +
client/lib/deltacloud/client/methods.rb | 29 +
client/lib/deltacloud/client/methods/address.rb | 68 ++
client/lib/deltacloud/client/methods/api.rb | 96 +++
.../client/methods/backward_compatiblity.rb | 72 +++
client/lib/deltacloud/client/methods/blob.rb | 91 +++
client/lib/deltacloud/client/methods/bucket.rb | 56 ++
client/lib/deltacloud/client/methods/common.rb | 46 ++
client/lib/deltacloud/client/methods/driver.rb | 54 ++
client/lib/deltacloud/client/methods/firewall.rb | 66 ++
.../deltacloud/client/methods/hardware_profile.rb | 42 ++
client/lib/deltacloud/client/methods/image.rb | 62 ++
client/lib/deltacloud/client/methods/instance.rb | 140 +++++
.../deltacloud/client/methods/instance_state.rb | 41 ++
client/lib/deltacloud/client/methods/key.rb | 59 ++
client/lib/deltacloud/client/methods/realm.rb | 43 ++
.../deltacloud/client/methods/storage_snapshot.rb | 62 ++
.../deltacloud/client/methods/storage_volume.rb | 95 +++
client/lib/deltacloud/client/models.rb | 30 +
client/lib/deltacloud/client/models/address.rb | 57 ++
client/lib/deltacloud/client/models/base.rb | 151 +++++
client/lib/deltacloud/client/models/blob.rb | 56 ++
client/lib/deltacloud/client/models/bucket.rb | 65 ++
client/lib/deltacloud/client/models/driver.rb | 87 +++
client/lib/deltacloud/client/models/firewall.rb | 70 ++
.../deltacloud/client/models/hardware_profile.rb | 68 ++
client/lib/deltacloud/client/models/image.rb | 60 ++
client/lib/deltacloud/client/models/instance.rb | 122 ++++
.../deltacloud/client/models/instance_address.rb | 35 +
.../lib/deltacloud/client/models/instance_state.rb | 52 ++
client/lib/deltacloud/client/models/key.rb | 52 ++
client/lib/deltacloud/client/models/realm.rb | 29 +
.../deltacloud/client/models/storage_snapshot.rb | 54 ++
.../lib/deltacloud/client/models/storage_volume.rb | 96 +++
client/lib/deltacloud/core_ext.rb | 19 +
client/lib/deltacloud/core_ext/element.rb | 32 +
client/lib/deltacloud/core_ext/fixnum.rb | 30 +
client/lib/deltacloud/core_ext/nil.rb | 22 +
client/lib/deltacloud/core_ext/string.rb | 49 ++
client/lib/deltacloud/error_response.rb | 92 +++
client/lib/documentation.rb | 59 --
client/lib/errors.rb | 140 -----
client/lib/hwp_properties.rb | 61 --
client/lib/instance_state.rb | 44 --
client/lib/string.rb | 59 --
client/support/method_test_template.erb | 53 ++
client/support/methods_template.erb | 54 ++
client/support/model_template.erb | 45 ++
62 files changed, 3304 insertions(+), 1442 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/.gitignore
----------------------------------------------------------------------
diff --git a/client/.gitignore b/client/.gitignore
index b4b07a3..3a0a93e 100644
--- a/client/.gitignore
+++ b/client/.gitignore
@@ -1,4 +1,19 @@
-spec_report.html
-*.swp
-tmp/
*.gem
+*.rbc
+.bundle
+.config
+coverage
+InstalledFiles
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
+*.lock
+
+# YARD artifacts
+.yardoc
+_yardoc
+doc/
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/Gemfile
----------------------------------------------------------------------
diff --git a/client/Gemfile b/client/Gemfile
new file mode 100644
index 0000000..4c6d274
--- /dev/null
+++ b/client/Gemfile
@@ -0,0 +1,12 @@
+source 'https://rubygems.org'
+
+gem 'faraday'
+gem 'nokogiri'
+
+group :development do
+ gem 'rake'
+ gem 'minitest'
+ gem 'vcr'
+ gem 'pry'
+ gem 'simplecov', :require => false
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/README
----------------------------------------------------------------------
diff --git a/client/README b/client/README
deleted file mode 100644
index 5324062..0000000
--- a/client/README
+++ /dev/null
@@ -1,127 +0,0 @@
-# Deltacloud Client (Ruby)
-
-The Deltacloud project includes a Ruby client. Other language-bindings
-are possible and will be supported soon. The client aims to insulate
-users from having to deal with HTTP and REST directly.
-
-Each resource type has an associated model to ease usage. Where
-resource reference other resources, natural navigation across the
-object model is possible.
-
-For example
-
- puts instance.image.name
- puts instance.hardware_profile.architecture
-
-## Basics
-
-To use the client, you must require `deltacloud`.
-
- require 'deltacloud'
-
-## Connecting to a Deltacloud provider
-
- require 'deltacloud'
-
- api_url = 'http://localhost:3001/api'
- api_name = 'mockuser'
- api_password = 'mockpassword'
-
- client = DeltaCloud.new( api_name, api_password, api_url )
-
- # work with client here
-
-In addition to creating a client, operations may occur within a block
-included on the initialization
-
- DeltaCloud.new( api_name, api_password, api_url ) do |client|
- # work with client here
- end
-
-In the event of a failure, any underlying HTTP transport exceptions
-will be thrown all the way out to the caller.
-
-## Listing realms
-
-You may retrieve a complete list of realms available to you
-
- realms = client.realms
-
-You may retrieve a specific realm by its identifier
-
- realm = client.realm( 'us' )
-
-## Listing hardware profiles
-
-You may retrieve a complete list of hardware profiles available for launching
-machines
-
- hwp = client.hardware_profiles
-
-You may filter hardware profiles by architecture
-
- flavors = client.hardware_profiles( :architecture=>'x86_64' )
-
-You may retrieve a specific hardware profile by its identifier
-
- flavor = client.hardware_profile( 'm1-small' )
-
-## Listing images
-
-You may retrieve a complete list of images
-
- images = client.images
-
-You may retrieve a list of images owned by the currently authenticated
-user
-
- images = client.images( :owner_id=>:self )
-
-You may retrieve a list of images visible to you but owned by a specific
-user
-
- images = client.images( :owner_id=>'daryll' )
-
-You may retrieve a specific image by its identifier
-
- image = client.image( 'ami-8675309' )
-
-## Listing instances
-
-You may retrieve a list of all instances visible to you
-
- instances = client.instances
-
-You may retrieve a specific instance by its identifier
-
- instance = client.instance( 'i-90125' )
-
-## Launching instances
-
-An instance may be launched using just an image identifier
-
- image = client.image( 'ami-8675309' )
- instance = client.create_instance( image.id )
-
-Optionally, a flavor or realm may be specified
-
- instance = client.create_instance( image.id, :flavor=>'m1-small', :realm=>'us' )
-
-## Manipulating instances
-
-Given an instance, depending on its state, various actions _may_ be available.
-
-To determine what's available, the `instance#actions` method may be used.
-
- instance.actions # [ 'reboot', 'stop' ]
-
-For a valid action, the method matching the action with an exclamation point may be called.
-
- instance.reboot!
-
-Upon invoking an action, the instance will refresh its contents, in case the state has changed.
-To determine later if the state has changed again, the instance must be refetched using
-the `client.instance(...)` method.
-
-
-
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/README.md
----------------------------------------------------------------------
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 0000000..fcd7595
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,73 @@
+# deltacloud-client
+
+The Deltacloud project includes a Ruby client. Other language-bindings
+are possible and will be supported soon. The client aims to insulate
+users from having to deal with HTTP and REST directly.
+
+Each resource type has an associated model to ease usage. Where
+resource reference other resources, natural navigation across the
+object model is possible.
+
+This is a Ruby client library for the [Deltacloud API](http://deltacloud.apache.org).
+
+## Usage
+
+```ruby
+require 'deltacloud/client'
+
+API_URL = "http://localhost:3001/api" # Deltacloud API endpoint
+
+# Simple use-cases
+client = Deltacloud::Client(API_URL, 'mockuser', 'mockpassword')
+
+pp client.instances # List all instances
+pp client.instance('i-12345') # Get one instance
+
+inst = client.create_instance 'ami-1234', :hwp_id => 'm1.small' # Create instance
+
+inst.reboot! # Reboot instance
+
+# Advanced usage
+
+# Deltacloud API supports changing driver per-request:
+
+client.use(:ec2, 'API_KEY', 'API_SECRET').instances # List EC2 instances
+client.use(:openstack, 'admin@tenant', 'password', KEYSTONE_URL).instances # List Openstack instances
+
+```
+# Want help?
+
+## Adding new Deltacloud collection to client
+
+```
+$ rake generate[YOUR_COLLECTION] # eg. 'storage_snapshot'
+# Hit Enter 2x
+```
+
+- Edit `lib/deltacloud/client/methods/YOUR_COLLECTION.rb` and add all
+ methods for manipulating your collection. The list/show methods
+ should already be generated for you, but double-check them.
+
+- Edit `lib/deltacloud/client/model/YOUR_COLLECTION.rb` and add model
+ methods. Model methods should really be just a syntax sugar and exercise
+ the *Deltacloud::Client::Methods* methods.
+ The purpose of *model* class life is to deserialize XML body received
+ from Deltacloud API to a Ruby class.
+
+## Debugging a nasty bug?
+
+- You can easily debug deltacloud-client using powerful **pry**.
+
+ - `gem install deltacloud-core`
+ - optional: `rbenv rehash` ;-)
+ - `deltacloudd -i mock -p 3002`
+ - `rake console`
+
+Console require **pry** gem installed. If you are not using this awesome
+gem, you can fix it by `gem install pry`.
+
+# License
+
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/Rakefile
----------------------------------------------------------------------
diff --git a/client/Rakefile b/client/Rakefile
index 4248ca5..17d0cdd 100644
--- a/client/Rakefile
+++ b/client/Rakefile
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
@@ -14,10 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+require 'rubygems'
require 'rubygems/package_task'
+require 'rake'
require 'rake/testtask'
-load 'deltacloud-client.gemspec'
+require 'pry' rescue LoadError
spec = Gem::Specification.load('deltacloud-client.gemspec')
@@ -25,15 +26,98 @@ Gem::PackageTask.new(spec) do |pkg|
pkg.need_tar = true
end
-desc "Re-install the deltacloud-client gem"
+desc "Re-install the deltacloud-client gem (used for development)"
task :reinstall do
puts %x{gem uninstall deltacloud-client --all -I -x}
puts %x{gem build deltacloud-client.gemspec}
puts %x{gem install deltacloud-client-*.gem --local}
end
+desc 'Generate model/methods files for collection.'
+task :generate, :name do |t, args|
+ require 'erb'
+ require_relative './lib/deltacloud/core_ext'
+ model_tpl = ERB.new(File.read('support/model_template.erb'))
+ methods_tpl = ERB.new(File.read('support/methods_template.erb'))
+ name = args[:name]
+ model_file = "lib/deltacloud/client/models/#{name}.rb"
+ methods_file = "lib/deltacloud/client/methods/#{name}.rb"
+ puts model_body = model_tpl.result(binding)
+ print "Save model to '#{model_file}'? [Y/n]"
+ answer = $stdin.gets.chomp
+ if answer.empty? or answer == 'Y'
+ File.open(model_file, 'w') { |f|
+ f.write(model_body)
+ }
+ File.open('lib/deltacloud/client/models.rb', 'a') { |f|
+ f.puts "require_relative './models/#{name}'"
+ }
+ end
+ puts methods_body = methods_tpl.result(binding)
+ print "Save methods to '#{methods_file}'? [Y/n]"
+ answer = $stdin.gets.chomp
+ if answer.empty? or answer == 'Y'
+ File.open(methods_file, 'w') { |f|
+ f.write(methods_body)
+ }
+ File.open('lib/deltacloud/client/methods.rb', 'a') { |f|
+ f.puts "require_relative './methods/#{name}'"
+ }
+ end
+ puts
+ puts "Don't forget to add this line to 'lib/deltacloud/client/connection.rb':"
+ puts
+ puts "include Deltacloud::Client::Methods::#{name.to_s.camelize}"
+ puts
+end
+
+desc 'Generate method test file'
+task :test_generate, :name do |t, args|
+ require 'erb'
+ require_relative './lib/deltacloud/core_ext'
+ method_tpl = ERB.new(File.read('support/method_test_template.erb'))
+ name = args[:name]
+ methods_file = "tests/methods/#{name}_test.rb"
+ puts method_body = method_tpl.result(binding)
+ print "Save method test to '#{methods_file}'? [Y/n]"
+ answer = $stdin.gets.chomp
+ if answer.empty? or answer == 'Y'
+ File.open(methods_file, 'w') { |f|
+ f.write(method_body)
+ }
+ end
+end
+
+
+desc "Open console with client connected to #{ENV['API_URL'] || 'localhost:3002/api'}"
+task :console do
+ unless binding.respond_to? :pry
+ puts 'To open a console, you need to have "pry" installed (gem install pry)'
+ exit(1)
+ end
+ require_relative './lib/deltacloud/client'
+ client = Deltacloud::Client(
+ ENV['API_URL'] || 'http://localhost:3002/api',
+ ENV['API_USER'] || 'mockuser',
+ ENV['API_PASSWORD'] || 'mockpassword'
+ )
+ binding.pry
+end
+
Rake::TestTask.new(:test) do |t|
t.test_files = FileList[
- 'tests/*test.rb', # EC2 frontend internal API tests
+ 'tests/*/*_test.rb'
]
end
+
+desc "Execute test against live Deltacloud API"
+task :test_live do
+ ENV['NO_VCR'] = 'true'
+ Rake::Task[:test].invoke
+end
+
+desc "Generate test coverage report"
+task :coverage do
+ ENV['COVERAGE'] = 'true'
+ Rake::Task[:test].invoke
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/deltacloud-client.gemspec
----------------------------------------------------------------------
diff --git a/client/deltacloud-client.gemspec b/client/deltacloud-client.gemspec
index 1b14f43..874e2f0 100644
--- a/client/deltacloud-client.gemspec
+++ b/client/deltacloud-client.gemspec
@@ -20,14 +20,19 @@ Gem::Specification.new do |s|
s.homepage = "http://www.deltacloud.org"
s.email = 'dev@deltacloud.apache.org'
s.name = 'deltacloud-client'
- s.description = %q{Deltacloud REST Client for API}
+ s.description = %q{A REST client for the Deltacloud API}
s.version = Deltacloud::API_VERSION
s.summary = %q{Deltacloud REST Client}
s.files = Dir['Rakefile', 'lib/**/*.rb']
s.test_files= Dir.glob("tests/**/**")
- s.extra_rdoc_files = Dir["LICENSE", "NOTICE", "DISCLAIMER"]
+ s.extra_rdoc_files = Dir["LICENSE", "NOTICE", "README.md"]
- s.add_dependency('rest-client', '>= 1.6.1')
+ s.add_dependency('faraday', '>=0.8.6')
s.add_dependency('nokogiri', '>= 1.4.3')
- s.add_development_dependency('rspec', '>= 2.0.0')
+
+ s.add_development_dependency('minitest')
+ s.add_development_dependency('simplecov')
+ s.add_development_dependency('vcr')
+ s.add_development_dependency('rake')
+ s.add_development_dependency('pry')
end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/base_object.rb
----------------------------------------------------------------------
diff --git a/client/lib/base_object.rb b/client/lib/base_object.rb
deleted file mode 100644
index 6a28d5a..0000000
--- a/client/lib/base_object.rb
+++ /dev/null
@@ -1,386 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership. The
-# ASF licenses this file to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance with the
-# License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-require_relative './string.rb'
-
-module DeltaCloud
-
- class BaseObjectParamError < Exception; end
- class NoHandlerForMethod < Exception; end
-
- # BaseObject model basically provide the basic operation around
- # REST model, like defining a links between different objects,
- # element with text values, or collection of these elements
- class BaseObject
- attr_reader :id, :url, :client, :base_name
- attr_reader :objects
-
- alias :uri :url
-
- # For initializing new object you require to set
- # id, url, client and name attribute.
- def initialize(opts={}, &block)
- @id, @url, @client, @base_name = opts[:id], opts[:url], opts[:client], opts[:name]
- @objects = []
- raise BaseObjectParamError if @id.nil? or @url.nil? or @client.nil? or @base_name.nil?
- yield self if block_given?
- end
-
- # This method add link to another object in REST model
- # XML syntax: <link rel="destroy" href="http://localhost/api/resource" method="post"/>
- def add_link!(object_name, id)
- @objects << {
- :type => :link,
- :method_name => object_name.sanitize,
- :id => id
- }
- @objects << {
- :type => :text,
- :method_name => "#{object_name.sanitize}_id",
- :value => id
- }
- end
-
- # Method add property for hardware profile
- def add_hwp_property!(name, property, type)
- hwp_property=case type
- when :float then DeltaCloud::HWP::FloatProperty.new(property, name)
- when :integer then DeltaCloud::HWP::Property.new(property, name)
- end
- @objects << {
- :type => :property,
- :method_name => name.sanitize,
- :property => hwp_property
- }
- end
-
- # This method define text object in REST model
- # XML syntax: <name>Instance 1</name>
- def add_text!(object_name, value)
- @objects << {
- :type => :text,
- :method_name => object_name.sanitize,
- :value => value
- }
- end
-
- def add_authentication!(auth_type, values=[])
- value = { :key => (values/'login/keyname').text.strip } if auth_type == 'key'
- if auth_type == 'password'
- value = {
- :username => (values/'login/username').text.strip,
- :username => (values/'login/password').text.strip
- }
- end
- @objects << {
- :type => :collection,
- :method_name => 'authentication',
- :values => value
- }
- end
-
- def add_provider!(provider_id, entrypoints)
- @providers ||= []
- @providers << {
- provider_id.intern => entrypoints.map { |e| { :kind => e[:kind], :url => e.text } }
- }
- @objects << {
- :type => :collection,
- :method_name => 'providers',
- :values => @providers
- }
- end
-
-
- # This method define collection of text elements inside REST model
- # XML syntax: <addresses>
- # <address>127.0.0.1</address>
- # <address>127.0.0.2</address>
- # </addresses>
- def add_addresses!(collection_name, values=[])
- @objects << {
- :type => :collection,
- :method_name => collection_name.sanitize,
- :values => values.collect { |v| { :address => v.text.strip, :type => v[:type] }}
- }
- end
-
- # This method define collection of text elements inside REST model
- # XML syntax: <addresses>
- # <address>127.0.0.1</address>
- # <address>127.0.0.2</address>
- # </addresses>
- def add_collection!(collection_name, values=[])
- @objects << {
- :type => :collection,
- :method_name => collection_name.sanitize,
- :values => values
- }
- end
-
- # Basic method hander. This define a way how value from property
- # will be returned
- def method_handler(m, args=[])
- case m[:type]
- when :link then return @client.send(m[:method_name].singularize, m[:id])
- when :text then return m[:value]
- when :property then return m[:property]
- when :collection then return m[:values]
- when :list then return m[:value].join(", ")
- else raise NoHandlerForMethod
- end
- end
-
- def method_missing(method_name, *args)
- # First of all search throught array for method name
- m = search_for_method(method_name)
- if m.nil?
- if method_name == :"valid_provider?"
- return providers.any? { |p| p.keys.include? args.first.to_sym }
- end
- if method_name == :"valid_provider_url?"
- return providers.any? { |p| !p.find { |k, v| v.find { |u| u[:url] == args.first } }.nil? }
- end
- super
- else
- # Call appropriate handler for method
- method_handler(m, args)
- end
- end
-
- # This method adds blobs to the blob_list property
- # of a bucket
- def add_blob!(blob_name)
- if @blob_list.nil?
- @blob_list = [blob_name]
- @objects << {
- :type => :list,
- :method_name => "blob_list",
- :value => @blob_list
- }
- else
- @blob_list << blob_name
- current = search_for_method('blob_list')
- current[:value] = @blob_list
- end
- end
-
- private
-
- def search_for_method(name)
- @objects.detect { |o| o[:method_name] == "#{name}" }
- end
-
- end
-
- class ActionObject < BaseObject
-
- def initialize(opts={}, &block)
- super(opts)
- @action_urls = opts[:action_urls] || []
- @actions = []
- end
-
- # This trigger is called right after action.
- # This method does nothing inside ActionObject
- # but it can be redifined and used in meta-programming
- def action_trigger(action)
- end
-
- def add_action_link!(id, link)
- m = {
- :type => :action_link,
- :method_name => "#{link['rel'].sanitize}!",
- :id => id,
- :href => link['href'],
- :rel => link['rel'].sanitize,
- :method => link['method'].sanitize
- }
- @objects << m
- @actions << [m[:rel], m[:href]]
- @action_urls << m[:href]
- end
-
- def actions
- @objects.inject([]) do |result, item|
- result << [item[:rel], item[:href]] if item[:type].eql?(:action_link)
- result
- end
- end
-
- def action_urls
- actions.collect { |a| a.last }
- end
-
- alias :base_method_handler :method_handler
-
- # First call BaseObject method handler,
- # then, if not method found try ActionObject handler
- def method_handler(m, args=[])
- begin
- base_method_handler(m, args)
- rescue NoHandlerForMethod
- case m[:type]
- when :action_link then do_action(m, args)
- else raise NoHandlerForMethod
- end
- end
- end
-
- alias :original_method_missing :method_missing
-
- def method_missing(name, *args)
- if name.to_s =~ /^has_(\w+)\?$/
- return actions.any? { |a| a[0] == $1 }
- end
- original_method_missing(name, args)
- end
-
- private
-
- def do_action(m, args)
- args = args.first || {}
- method = m[:method].to_sym
- @client.request(method,
- m[:href],
- method == :get ? args : {},
- method == :get ? {} : args)
- action_trigger(m[:rel])
- end
-
- end
-
- class StatefulObject < ActionObject
- attr_reader :state
-
- def initialize(opts={}, &block)
- super(opts)
- @state = opts[:initial_state] || ''
- add_default_states!
- end
-
- def add_default_states!
- @objects << {
- :method_name => 'stopped?',
- :type => :state,
- :state => 'STOPPED'
- }
- @objects << {
- :method_name => 'running?',
- :type => :state,
- :state => 'RUNNING'
- }
- @objects << {
- :method_name => 'pending?',
- :type => :state,
- :state => 'PENDING'
- }
- @objects << {
- :method_name => 'shutting_down?',
- :type => :state,
- :state => 'SHUTTING_DOWN'
- }
- end
-
- def action_trigger(action)
- # Refresh object state after action unless the object was destroyed
- return if action.to_s == "destroy"
- @new_state_object = @client.send(self.base_name, self.id)
- @state = @new_state_object.state
- self.update_actions!
- end
-
- def add_run_action!(id, link)
- @objects << {
- :method_name => 'run',
- :type => :run,
- :url => link,
- }
- end
-
- alias :action_method_handler :method_handler
-
- def method_handler(m, args=[])
- begin
- action_method_handler(m, args)
- rescue NoHandlerForMethod
- case m[:type]
- when :state then evaluate_state(m[:state], @state)
- when :run then run_command(m[:url][:href], args)
- else raise NoHandlerForMethod
- end
- end
- end
-
-# private
-
- def run_command(instance_url, args)
- credentials = args[1]
- params = {
- :cmd => args[0],
- :private_key => credentials[:pem] ? File.read(credentials[:pem]) : nil,
- }
- params.merge!({
- :username => credentials[:username],
- :password => credentials[:password]
- }) if credentials[:username] and credentials[:password]
- @client.request(:post, instance_url, {}, params) do |response|
- output = Nokogiri::XML(response)
- (output/'/instance/output').first.text
- end
- end
-
- def evaluate_state(method_state, current_state)
- method_state.eql?(current_state)
- end
-
- def action_objects
- @objects.select { |o| o[:type] == :action_link }
- end
-
- def update_actions!
- new_actions = @new_state_object.action_objects
- @objects.reject! { |o| o[:type] == :action_link }
- @objects = (@objects + new_actions)
- end
-
- end
-
- def self.add_class(name, parent=:base)
- parent = parent.to_s
- parent_class = "#{parent.classify}Object"
- @defined_classes ||= []
- class_name = "#{parent.classify}::#{name.classify}"
- unless @defined_classes.include?(class_name)
- DeltaCloud::API.class_eval("class #{class_name} < DeltaCloud::#{parent_class}; end")
- @defined_classes << class_name
- end
-
- DeltaCloud::API.const_get(parent.classify).const_get(name.classify)
- end
-
- def self.guess_model_type(response)
- response = Nokogiri::XML(response.to_s)
- return :action if ((response/'//actions').length >= 1) and ((response/'//state').length == 0)
- return :stateful if ((response/'//actions').length >= 1) and ((response/'//state').length >= 1)
- return :base
- end
-
- class API
- class Action; end
- class Base; end
- class Stateful; end
- end
-end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/client_bucket_methods.rb
----------------------------------------------------------------------
diff --git a/client/lib/client_bucket_methods.rb b/client/lib/client_bucket_methods.rb
deleted file mode 100644
index a3dfda0..0000000
--- a/client/lib/client_bucket_methods.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership. The
-# ASF licenses this file to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance with the
-# License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-module ClientBucketMethods
-
- def create_bucket(params)
- obj = nil
- request(:post, "#{api_uri.to_s}/buckets", {:name => params['id'],:location=>params['bucket_location'] }) do |response|
- handle_backend_error(response) if response.code!=201
- obj = base_object(:bucket, response)
- end
- end
-
- def destroy_bucket(params)
- #actually response here is 204 - no content - so nothing returned to client?
- request(:delete, "#{api_uri.to_s}/buckets/#{params['id']}") do |response|
- handle_backend_error(response) if response.code!=204
- nil if response.code == 204
- end
- end
-
- def create_blob(params)
- blob = nil
- resource = RestClient::Resource.new("#{api_uri.to_s}/buckets/#{params['bucket']}", :open_timeout => 10, :timeout => 45)
- headers = default_headers.merge(extended_headers)
- unless params['metadata'].nil?
- metadata_headers = {}
- params['metadata'].each do |k,v|
- metadata_headers["X-Deltacloud-Blobmeta-#{k}"] = v
- end
- headers = headers.merge(metadata_headers)
- end
- resource.send(:post, {:blob_data => File.new(params['file_path'], 'rb'), :blob_id => params[:id]}, headers) do |response, request, block|
- handle_backend_error(response) if response.code.eql?(500)
- blob = base_object(:blob, response)
- yield blob if block_given?
- end
- return blob
- end
-
- def destroy_blob(params)
- request(:delete, "#{api_uri.to_s}/buckets/#{params['bucket']}/#{params[:id]}") do |response|
- handle_backend_error(response) if response.code!=204
- nil if response.code == 204
- end
- end
-
- #RestClient doesn't do streaming 'get' yet - we already opened a pull request on this see
- #https://github.com/archiloque/rest-client/issues/closed#issue/62 - apparently its going to
- #be in the next version - unknown when. For now get full response. FIXME
- def blob_data(params)
- request(:get, "#{api_uri.to_s}/buckets/#{params['bucket']}/#{params[:id]}/content") do |response|
- response
- end
- end
-
-end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud.rb b/client/lib/deltacloud.rb
deleted file mode 100644
index a58c280..0000000
--- a/client/lib/deltacloud.rb
+++ /dev/null
@@ -1,486 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership. The
-# ASF licenses this file to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance with the
-# License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-require 'nokogiri'
-require 'rest_client'
-require 'base64'
-require 'logger'
-require 'require_relative' if RUBY_VERSION =~ /^1\.8/
-require_relative './hwp_properties.rb'
-require_relative './instance_state.rb'
-require_relative './documentation.rb'
-require_relative './base_object.rb'
-require_relative './errors.rb'
-require_relative './client_bucket_methods.rb'
-
-module DeltaCloud
-
- # Get a new API client instance
- #
- # @param [String, user_name] API user name
- # @param [String, password] API password
- # @param [String, url] API URL (eg. http://localhost:3001/api)
- # @return [DeltaCloud::API]
- #def self.new(user_name, password, api_url, opts={}, &block)
- # opts ||= {}
- # API.new(user_name, password, api_url, opts, &block)
- #end
-
- def self.new(user_name, password, api_url, &block)
- API.new(user_name, password, api_url, &block)
- end
-
- # Check given credentials if their are valid against
- # backend cloud provider
- #
- # @param [String, user_name] API user name
- # @param [String, password] API password
- # @param [String, user_name] API URL (eg. http://localhost:3001/api)
- # @return [true|false]
- def self.valid_credentials?(user_name, password, api_url, opts={})
- api=API.new(user_name, password, api_url, opts)
- result = false
- api.request(:get, '', :force_auth => '1') do |response|
- result = true if response.code.eql?(200)
- end
- return result
- end
-
- # Return a API driver for specified URL
- #
- # @param [String, url] API URL (eg. http://localhost:3001/api)
- def self.driver_name(url)
- API.new(nil, nil, url).driver_name
- end
-
- class API
- attr_reader :api_uri, :driver_name, :api_version, :features, :entry_points
- attr_reader :api_driver, :api_provider
-
- 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
-
- # 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
- 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
-
- def connect(&block)
- yield self
- end
-
- # Return API hostname
- def api_host; @api_uri.host ; end
-
- # Return API port
- def api_port; @api_uri.port ; end
-
- # Return API path
- def api_path; @api_uri.path ; end
-
- # Define methods based on 'rel' attribute in entry point
- # Two methods are declared: 'images' and 'image'
- 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 =~ /\/#{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
-
- 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
-
- # Add default attributes [id and href] to class
- 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
-
- # Convert XML response to defined Ruby Class
- 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'] =~ /^(\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!(/(\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
-
- # Get /api and parse entry points
- 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
-
- # Generate create_* methods dynamically
- #
- def method_missing(name, *args)
- if name.to_s =~ /^([\w_]+)_ids$/
- return self.send(:"#{$1.pluralize}").map { |o| o.id }
- end
- if name.to_s =~ /^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
-
- 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
-
- def use_config!(opts={})
- @api_uri = URI.parse(opts[:url]) if opts[:url]
- use_driver(opts[:driver], opts)
- end
-
- 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
-
- def response_successful?(code)
- return true if code.to_s =~ /^2(\d{2})$/
- return true if code.to_s =~ /^3(\d{2})$/
- return false
- end
-
- def response_error(response)
- xml = Nokogiri::XML(response.to_s)
- if (xml/'message').empty? and response.code.to_s =~ /4(\d{2})/
- DeltaCloud::HTTPError.client_error(response.code)
- else
- opts = {
- :params => (xml/'request/param').inject({}) { |r,p| r[:"#{p[:name]}"] = p.text; r }
- }
- if backend_node = xml.at_xpath('/error/backend')
- opts[:driver] = backend_node[:driver]
- opts[:provider] = backend_node[:provider]
- end
- 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
-
- # Basic request method
- #
- def request(*args, &block)
- conf = {
- :method => (args[0] || 'get').to_sym,
- :path => (args[1]=~/^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
-
-
- # Check if specified collection have wanted feature
- def feature?(collection, name)
- @features.has_key?(collection) && @features[collection].include?(name)
- end
-
- # List available instance states and transitions between them
- 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
-
- # Select instance state specified by name
- def instance_state(name)
- instance_states.select { |s| s.name.to_s.eql?(name.to_s) }.first
- end
-
- # Skip parsing /api when we already got entry points
- def discovered?
- true if @entry_points!={}
- end
-
- # This method will retrieve API documentation for given collection
- 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
-
- private
-
- def default_headers
- # The linebreaks inserted every 60 characters in the Base64
- # encoded header cause problems under JRuby
- auth_header = "Basic "+Base64.encode64("#{@username}:#{@password}")
- auth_header.gsub!("\n", "")
- {
- :authorization => auth_header,
- :accept => "application/xml"
- }
- end
-
- end
-
-end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client.rb b/client/lib/deltacloud/client.rb
new file mode 100644
index 0000000..36200ad
--- /dev/null
+++ b/client/lib/deltacloud/client.rb
@@ -0,0 +1,78 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud
+ module Client
+ require 'require_relative' if RUBY_VERSION < '1.9'
+ require 'ostruct'
+ require 'nokogiri'
+ require 'faraday'
+
+ # Core extensions
+ require_relative './core_ext'
+
+ # Errors && Helpers
+ require_relative './client/helpers/model_helper'
+ require_relative './client/helpers/xml_helper'
+ require_relative './client/helpers/property_helper'
+
+ # Exceptions goes here
+ require_relative './client/base_error'
+
+ # Faraday Middleware for Deltacloud errors
+ require_relative './error_response'
+
+ # Deltacloud API methods
+ require_relative './client/methods/api'
+ require_relative './client/methods/backward_compatiblity'
+
+ # Extend Client module with methods that existed in old client
+ # and we want to keep them.
+ # Deprecation warnings should be provided to users if they use something
+ # from these modules.
+ #
+ extend Deltacloud::Client::Methods::BackwardCompatibility::ClassMethods
+
+ # Deltacloud methods
+ require_relative './client/methods'
+
+ # Deltacloud models
+ require_relative './client/models'
+
+ require_relative './client/connection'
+
+ VERSION = '1.1.2'
+
+ # Check if the connection to Deltacloud API is valid
+ def self.valid_connection?(api_url)
+ begin
+ Deltacloud::Client(api_url, '', '') && true
+ rescue Faraday::Error::ConnectionFailed
+ false
+ rescue Deltacloud::Client::AuthenticationError
+ false
+ end
+ end
+
+ end
+
+ def self.Client(url, api_user, api_password, opts={})
+ Client::Connection.new({
+ :url => url,
+ :api_user => api_user,
+ :api_password => api_password
+ }.merge(opts))
+ end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/base_error.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/base_error.rb b/client/lib/deltacloud/client/base_error.rb
new file mode 100644
index 0000000..544aa90
--- /dev/null
+++ b/client/lib/deltacloud/client/base_error.rb
@@ -0,0 +1,84 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+
+ # Reporting internal client errors
+ #
+ class Error < StandardError; end
+
+ class BaseError < Error
+ attr_reader :server_backtrace
+ attr_reader :driver
+ attr_reader :provider
+ attr_reader :status
+
+ def initialize(opts={})
+ if opts.is_a? Hash
+ @server_backtrace = opts[:server_backtrace]
+ @driver = opts[:driver]
+ @provider = opts[:provider]
+ @status = opts[:status]
+ @original_error = opts[:original_error]
+ super(opts[:message])
+ else
+ super(opts)
+ end
+ end
+
+ # Return the original XML error message received from Deltacloud API
+ def original_error
+ @original_error
+ end
+
+ # If the Deltacloud API server error response contain backtrace from
+ # server,then make this backtrace available as part of this exception
+ # backtrace
+ #
+ def set_backtrace(backtrace)
+ return super(backtrace) if @server_backtrace.nil?
+ super([
+ backtrace[0..3],
+ "-------Deltacloud API backtrace-------",
+ @server_backtrace.split[0..10],
+ ].flatten)
+ end
+
+ end
+
+ # Report 401 errors
+ class AuthenticationError < BaseError; end
+
+ # Report 502 errors (back-end cloud provider encounter error)
+ class BackendError < BaseError; end
+
+ # Report 5xx errors (error on Deltacloud API server)
+ class ServerError < BaseError; end
+
+ # Report 501 errors (collection or operation is not supported)
+ class NotSupported < ServerError; end
+
+ # Report 4xx failures (client failures)
+ class ClientFailure < BaseError; end
+
+ # Report 404 error (object not found)
+ class NotFound < BaseError; end
+
+ # Report 405 failures (resource state does not permit the requested operation)
+ class InvalidState < ClientFailure; end
+
+ # Report this when client do Image#launch using incompatible HWP
+ class IncompatibleHardwareProfile < ClientFailure; end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/connection.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/connection.rb b/client/lib/deltacloud/client/connection.rb
new file mode 100644
index 0000000..ad91cb0
--- /dev/null
+++ b/client/lib/deltacloud/client/connection.rb
@@ -0,0 +1,135 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+ class Connection
+
+ attr_accessor :connection
+ attr_reader :request_driver
+ attr_reader :request_provider
+ attr_reader :entrypoint
+
+ include Deltacloud::Client::Helpers::Model
+
+ include Deltacloud::Client::Methods::Common
+ include Deltacloud::Client::Methods::Api
+ include Deltacloud::Client::Methods::BackwardCompatibility
+ include Deltacloud::Client::Methods::Driver
+ include Deltacloud::Client::Methods::Realm
+ include Deltacloud::Client::Methods::HardwareProfile
+ include Deltacloud::Client::Methods::Image
+ include Deltacloud::Client::Methods::Instance
+ include Deltacloud::Client::Methods::InstanceState
+ include Deltacloud::Client::Methods::Key
+ include Deltacloud::Client::Methods::StorageVolume
+ include Deltacloud::Client::Methods::StorageSnapshot
+ include Deltacloud::Client::Methods::Address
+ include Deltacloud::Client::Methods::Bucket
+ include Deltacloud::Client::Methods::Blob
+ include Deltacloud::Client::Methods::Firewall
+
+ def initialize(opts={})
+ @request_driver = opts[:driver]
+ @request_provider = opts[:provider]
+ @connection = Faraday.new(:url => opts[:url]) do |f|
+ # NOTE: The order of this is somehow important for VCR
+ # recording.
+ f.request :url_encoded
+ f.headers = deltacloud_request_headers
+ f.basic_auth opts[:api_user], opts[:api_password]
+ f.use Deltacloud::ErrorResponse
+ f.adapter :net_http
+ end
+ cache_entrypoint!
+ @request_driver ||= current_driver
+ @request_provider ||= current_provider
+ end
+
+ # Change the current driver and return copy of the client
+ # This allows chained calls like: client.driver(:ec2).instances
+ #
+ # - driver_id -> The new driver id (:mock, :ec2, :rhevm, ...)
+ # - api_user -> API user name
+ # - api_password -> API password
+ # - api_provider -> API provider (aka API_PROVIDER string)
+ #
+ def use(driver_id, api_user, api_password, api_provider=nil, &block)
+ new_client = self.class.new(
+ :url => @connection.url_prefix.to_s,
+ :api_user => api_user,
+ :api_password => api_password,
+ :provider => api_provider,
+ :driver => driver_id
+ )
+ new_client.cache_entrypoint!
+ yield new_client if block_given?
+ new_client
+ end
+
+ # Change the API provider but keep the current client credentials.
+ # This allows to change the EC2 region and list instances in that
+ # region without need to supply credentials.
+ #
+ # client.use_provider('eu-west-1') { |p| p.instances }
+ #
+ # - provider_id -> API provider (aka API_PROVIDER)
+ #
+ def use_provider(provider_id, &block)
+ new_client = self.clone
+ new_connection = @connection.clone
+ new_connection.headers['X-Deltacloud-Provider'] = provider_id
+ new_client.connection = new_connection
+ new_client.cache_entrypoint!(true)
+ yield new_client if block_given?
+ new_client
+ end
+
+ # Cache the API entrypoint (/api) for the current connection,
+ # so we don't need to query /api everytime we ask if certain
+ # collection/operation is supported
+ #
+ # - force -> If 'true' force to refresh stored cached entrypoint
+ #
+ def cache_entrypoint!(force=false)
+ @entrypoint = nil if force
+ @entrypoint ||= connection.get(path).body
+ end
+
+ # Check if the credentials used are valid for the current @connection
+ #
+ def valid_credentials?
+ begin
+ r = connection.get(path, { :force_auth => 'true' })
+ r.status == 200
+ rescue error(:authentication_error)
+ false
+ end
+ end
+
+ private
+
+ # Default Deltacloud HTTP headers. Common for *all* requests
+ # to Deltacloud API
+ #
+ def deltacloud_request_headers
+ headers = {}
+ headers['Accept'] = 'application/xml'
+ headers['X-Deltacloud-Driver'] = @request_driver.to_s if @request_driver
+ headers['X-Deltacloud-Provider'] = @request_provider.to_s if @request_provider
+ headers
+ end
+
+ end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/helpers/model_helper.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/helpers/model_helper.rb b/client/lib/deltacloud/client/helpers/model_helper.rb
new file mode 100644
index 0000000..2224d0d
--- /dev/null
+++ b/client/lib/deltacloud/client/helpers/model_helper.rb
@@ -0,0 +1,69 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+ module Helpers
+ module Model
+
+ # Retrieve the class straight from
+ # Deltacloud::Client model.
+ #
+ # -name -> A class name in underscore form (:storage_volume)
+ #
+ def model(name)
+ if name.nil? or (!name.nil? and name.empty?)
+ raise error.new("The model name can't be blank")
+ end
+ Deltacloud::Client.const_get(name.to_s.camelize)
+ end
+
+ # Syntax sugar method for retrieving various Client
+ # exception classes.
+ #
+ # - name -> Exception class name in underscore
+ #
+ # NOTE: If name is 'nil' the default Error exception
+ # will be returned
+ #
+ def error(name=nil)
+ model(name || :error)
+ end
+
+ # Checks if current @connection support +model_name+
+ # and then convert HTTP response to a Ruby model
+ #
+ # - model_name -> A class name in underscore form
+ # - collection_body -> HTTP body of collection
+ #
+ def from_collection(model_name, collection_body)
+ must_support!(model_name)
+ model(model_name.to_s.singularize).from_collection(
+ self,
+ collection_body
+ )
+ end
+
+ # Check if the collection for given model is supported
+ # in current @connection and then parse/convert
+ # resource XML to a Ruby class
+ #
+ def from_resource(model_name, resource_body)
+ must_support!(model_name.to_s.pluralize)
+ model(model_name).convert(self, resource_body)
+ end
+
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/helpers/property_helper.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/helpers/property_helper.rb b/client/lib/deltacloud/client/helpers/property_helper.rb
new file mode 100644
index 0000000..a30ab9a
--- /dev/null
+++ b/client/lib/deltacloud/client/helpers/property_helper.rb
@@ -0,0 +1,103 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+ module Helpers
+ module Property
+
+ class Property
+ attr_reader :name, :unit, :default
+
+ def initialize(name, unit, default=nil)
+ @name = name
+ @unit = unit
+ @default = default
+ end
+
+ def value
+ @default || 'opaque'
+ end
+
+ def self.parse(body)
+ Property.new(body['name'], body['unit'], body['value'])
+ end
+
+ def kind
+ self.class.name.split('::').last.downcase.to_sym
+ end
+
+ end
+
+ class Range < Property
+
+ attr_reader :first, :last
+
+ def initialize(name, unit, first, last, default=nil)
+ @first, @last = first, last
+ super(name, unit, default)
+ end
+
+ def value
+ ::Range.new(@first.to_i, @last.to_i)
+ end
+
+ def self.parse(body)
+ base = super
+ new(base.name, base.unit, body.at('range')['first'], body.at('range')['last'], base.default)
+ end
+
+ end
+
+ class Enum < Property
+ include Enumerable
+ attr_reader :values
+
+ def initialize(name, unit, values, default=nil)
+ @values = values
+ super(name, unit, default)
+ end
+
+ def value
+ @values
+ end
+
+ def each
+ value.each
+ end
+
+ def self.parse(body)
+ base = super
+ new(base.name, base.unit, body.xpath('enum/entry').map { |e| e['value'] }, base.default)
+ end
+ end
+
+ class Fixed < Property
+ attr_reader :value
+
+ def initialize(name, unit, value)
+ @value = value
+ super(name, unit, @value)
+ end
+
+ def self.parse(body)
+ base = super
+ new(base.name, base.unit, body['value'])
+ end
+
+ end
+
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/helpers/xml_helper.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/helpers/xml_helper.rb b/client/lib/deltacloud/client/helpers/xml_helper.rb
new file mode 100644
index 0000000..5874c00
--- /dev/null
+++ b/client/lib/deltacloud/client/helpers/xml_helper.rb
@@ -0,0 +1,33 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+ module Helpers
+ module XmlHelper
+
+ # Extract XML string from the various objects
+ #
+ def extract_xml_body(obj)
+ case obj
+ when Faraday::Response then obj.body
+ when Nokogiri::XML::Element then obj.to_s
+ when Nokogiri::XML::Document then obj.to_s
+ else obj
+ end
+ end
+
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/methods.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/methods.rb b/client/lib/deltacloud/client/methods.rb
new file mode 100644
index 0000000..0897866
--- /dev/null
+++ b/client/lib/deltacloud/client/methods.rb
@@ -0,0 +1,29 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+require_relative './methods/common'
+require_relative './methods/driver'
+require_relative './methods/realm'
+require_relative './methods/hardware_profile'
+require_relative './methods/image'
+require_relative './methods/instance'
+require_relative './methods/instance_state'
+require_relative './methods/storage_volume'
+require_relative './methods/storage_snapshot'
+require_relative './methods/key'
+require_relative './methods/address'
+require_relative './methods/bucket'
+require_relative './methods/blob'
+require_relative './methods/firewall'
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/methods/address.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/methods/address.rb b/client/lib/deltacloud/client/methods/address.rb
new file mode 100644
index 0000000..21da5f6
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/address.rb
@@ -0,0 +1,68 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+ module Methods
+ module Address
+
+ # Retrieve list of all address entities
+ #
+ # Filter options:
+ #
+ # - :id -> Filter entities using 'id' attribute
+ #
+ def addresses(filter_opts={})
+ from_collection :addresses,
+ connection.get(api_uri('addresses'), filter_opts)
+ end
+
+ # Retrieve the single address entity
+ #
+ # - address_id -> Address entity to retrieve
+ #
+ def address(address_id)
+ from_resource :address,
+ connection.get(api_uri("addresses/#{address_id}"))
+ end
+
+ # Create a new address
+ #
+ def create_address
+ create_resource :address, {}
+ end
+
+ def destroy_address(address_id)
+ destroy_resource :address, address_id
+ end
+
+ def associate_address(address_id, instance_id)
+ result = connection.post(
+ api_uri("/addresses/#{address_id}/associate")
+ ) do |request|
+ request.params = { :instance_id => instance_id }
+ end
+ result.status == 202
+ end
+
+ def disassociate_address(address_id)
+ result = connection.post(
+ api_uri("/addresses/#{address_id}/disassociate")
+ )
+ result.status == 202
+ end
+
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/methods/api.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/methods/api.rb b/client/lib/deltacloud/client/methods/api.rb
new file mode 100644
index 0000000..32bd048
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/api.rb
@@ -0,0 +1,96 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+ module Methods
+ module Api
+
+ def path
+ connection.url_prefix.path
+ end
+
+ def api_uri(uri)
+ URI.parse([path, uri.gsub(/^(\/+)/,'')].join('/'))
+ end
+
+ # The current version of Deltacloud API
+ #
+ def version
+ entrypoint.to_xml.root['version']
+ end
+
+ alias_method :api_version, :version
+
+ # The current driver the @connection is using
+ #
+ def current_driver
+ entrypoint.to_xml.root['driver']
+ end
+
+ alias_method :api_driver, :current_driver
+ alias_method :driver_name, :current_driver
+
+ # The current provider the @connection is using
+ #
+ def current_provider
+ entrypoint.to_xml.root['provider']
+ end
+
+ # List of the currently supported collections by @connection
+ #
+ def supported_collections
+ entrypoint.to_xml.root.xpath('link').map { |l| l['rel'] }
+ end
+
+ alias_method :entrypoints, :supported_collections
+
+ # Syntax sugar for +supported_collections+
+ # Return 'true' if the collection is supported by current API entrypoint
+ #
+ def support?(collection)
+ supported_collections.include? collection.to_s
+ end
+
+ # Syntax sugar for Method modules, where you can 'require' the support
+ # for the given collection before you execute API call
+ #
+ # Raise +NotSupported+ exception if the given +collection+ is not
+ # supported
+ #
+ def must_support!(collection)
+ unless support?(collection)
+ raise error(:not_supported).new("Collection '#{collection}' not supported by current API endpoint.")
+ end
+ end
+
+ # +Hash+ of all features supported by current connection
+ #
+ def features
+ entrypoint.to_xml.root.xpath('link/feature').inject(Hash.new(Array.new)) do |result, feature|
+ result[feature.parent['rel']] += [feature['name']]
+ result
+ end
+ end
+
+ # Check if the current collection support given feature for given
+ # collection
+ #
+ def feature?(collection_name, feature_name)
+ features[collection_name.to_s].include?(feature_name.to_s)
+ end
+
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/methods/backward_compatiblity.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/methods/backward_compatiblity.rb b/client/lib/deltacloud/client/methods/backward_compatiblity.rb
new file mode 100644
index 0000000..8718836
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/backward_compatiblity.rb
@@ -0,0 +1,72 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+ module Methods
+ module BackwardCompatibility
+
+ # Backward compatibility methods provides fallback for the
+ # old deltacloud-client gem.
+ #
+ #
+ def api_host
+ connection.url_prefix.host
+ end
+
+ def api_port
+ connection.url_prefix.port
+ end
+
+ def connect(&block)
+ yield self.clone
+ end
+
+ def with_config(opts, &block)
+ yield inst = use(
+ opts[:driver],
+ opts[:username],
+ opts[:password],
+ opts[:provider]
+ ) if block_given?
+ inst
+ end
+
+ def use_driver(new_driver, opts={})
+ with_config(opts.merge(:driver => new_driver))
+ end
+
+ alias_method :"use_config!", :use_driver
+
+ def discovered?
+ true unless entrypoint.nil?
+ end
+
+ module ClassMethods
+
+ def valid_credentials?(api_user, api_password, api_url, opts={})
+ args = {
+ :api_user => api_user,
+ :api_password => api_password,
+ :url => api_url
+ }
+ args.merge!(:providers => opts[:provider]) if opts[:provider]
+ Deltacloud::Client::Connection.new(args).valid_credentials?
+ end
+
+ end
+
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/methods/blob.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/methods/blob.rb b/client/lib/deltacloud/client/methods/blob.rb
new file mode 100644
index 0000000..80a3945
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/blob.rb
@@ -0,0 +1,91 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+ module Methods
+ module Blob
+
+ # Retrieve list of all blob entities from given bucket
+ #
+ def blobs(bucket_id=nil)
+ raise error.new("The :bucket_id cannot be nil.") if bucket_id.nil?
+ bucket(bucket_id).blob_ids.map { |blob_id| blob(bucket_id, blob_id) }
+ end
+
+ # Retrieve the single blob entity
+ #
+ # - blob_id -> Blob entity to retrieve
+ #
+ def blob(bucket_id, blob_id)
+ model(:blob).convert(
+ self,
+ connection.get(api_uri("buckets/#{bucket_id}/#{blob_id}"))
+ )
+ end
+
+ # Create a new blob
+ #
+ # - bucket_id -> A bucket ID that new blob should belong to
+ # - blob_name -> A name for new blob
+ # - blob_data -> Data stored in this blob
+ # - create_opts
+ # - :user_metadata -> A Ruby +Hash+ with key => value metadata
+ #
+ def create_blob(bucket_id, blob_name, blob_data, create_opts={})
+ must_support! :buckets
+ create_opts.merge!(convert_meta_params(create_opts.delete(:user_metadata)))
+ response = connection.post(api_uri("buckets/#{bucket_id}")) do |request|
+ request.params = create_opts.merge(
+ :blob_id => blob_name,
+ :blob_data => blob_data
+ )
+ end
+ model(:blob).convert(self, response.body)
+ end
+
+ # Destroy given bucket blob
+ #
+ def destroy_blob(bucket_id, blob_id)
+ must_support! :buckets
+ r = connection.delete(api_uri("buckets/#{bucket_id}/#{blob_id}"))
+ r.status == 204
+ end
+
+ private
+
+ # Convert the user_metadata into POST params compatible with
+ # blob creation
+ #
+ # - params -> Simple Ruby +Hash+
+ #
+ # @return { :meta_params => COUNTER, :meta_name1 => '', :meta_value1 => ''}
+ #
+ def convert_meta_params(params)
+ meta_params = {}
+ counter = 0
+ (params || {}).each do |key, value|
+ counter += 1
+ meta_params["meta_name#{counter}"] = key
+ meta_params["meta_value#{counter}"] = value
+ end
+ if counter >= 1
+ meta_params.merge!(:meta_params => counter.to_s)
+ end
+ meta_params
+ end
+
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/methods/bucket.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/methods/bucket.rb b/client/lib/deltacloud/client/methods/bucket.rb
new file mode 100644
index 0000000..b68cb05
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/bucket.rb
@@ -0,0 +1,56 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+ module Methods
+ module Bucket
+
+ # Retrieve list of all bucket entities
+ #
+ # Filter options:
+ #
+ # - :id -> Filter entities using 'id' attribute
+ #
+ def buckets(filter_opts={})
+ from_collection :buckets,
+ connection.get(api_uri('buckets'), filter_opts)
+ end
+
+ # Retrieve the single bucket entity
+ #
+ # - bucket_id -> Bucket entity to retrieve
+ #
+ def bucket(bucket_id)
+ from_resource :bucket,
+ connection.get(api_uri("buckets/#{bucket_id}"))
+ end
+
+ # Create a new bucket
+ #
+ # - create_opts
+ #
+ def create_bucket(name)
+ create_resource :bucket, :name => name
+ end
+
+ # Destroy given bucket
+ #
+ def destroy_bucket(bucket_id)
+ destroy_resource :bucket, bucket_id
+ end
+
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/deltacloud/blob/b6d3365c/client/lib/deltacloud/client/methods/common.rb
----------------------------------------------------------------------
diff --git a/client/lib/deltacloud/client/methods/common.rb b/client/lib/deltacloud/client/methods/common.rb
new file mode 100644
index 0000000..250e3a4
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/common.rb
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Client
+ module Methods
+ module Common
+
+ # A generic method for creating a new resources
+ #
+ # - resource_name -> A resource name to create (eg. :image)
+ # - create_opts -> HTTP options to pass into the create operation
+ #
+ def create_resource(resource_name, create_opts={})
+ no_convert_model = create_opts.delete(:no_convert_model)
+ must_support! resource_name.to_s.pluralize
+ response = connection.post(api_uri(resource_name.to_s.pluralize)) do |request|
+ request.params = create_opts
+ end
+ no_convert_model ? response : model(resource_name).convert(self, response.body)
+ end
+
+ # A generic method for destroying resources
+ #
+ def destroy_resource(resource_name, resource_id)
+ must_support! resource_name.to_s.pluralize
+ result = connection.delete(
+ api_uri([resource_name.to_s.pluralize, resource_id].join('/'))
+ )
+ result.status.is_no_content?
+ end
+
+ end
+ end
+end