Skip to content
Browse files

Merge pull request #58 from arax/client-dev

Fixes #52 by allowing relative resource paths
  • Loading branch information...
2 parents b21e760 + fa549ec commit 5066c4839e7bffe447d673407b7e81cde9d2f935 @ffeldhaus ffeldhaus committed Feb 22, 2013
View
2 Gemfile
@@ -1,4 +1,4 @@
-source :rubygems
+source "https://rubygems.org/"
gemspec
View
60 Gemfile.lock
@@ -7,18 +7,18 @@ GIT
GIT
remote: git://github.com/jruby/warbler.git
- revision: 75da5228c39d0e1f93599fb61db2575f2369fee7
+ revision: ce3ce4df137504822e4cbb9399dee7e7dd767c44
specs:
warbler (1.3.7.dev)
- jruby-jars (>= 1.4.0)
+ jruby-jars (>= 1.5.6)
jruby-rack (>= 1.0.0)
- rake (>= 0.8.7)
- rubyzip (>= 0.9.4)
+ rake (>= 0.9.6)
+ rubyzip (>= 0.9.8)
PATH
remote: .
specs:
- occi (3.0.0)
+ occi (3.1.0.alpha.1)
activesupport
amqp
antlr3
@@ -31,19 +31,19 @@ PATH
uuidtools (>= 2.1.3)
GEM
- remote: http://rubygems.org/
+ remote: https://rubygems.org/
specs:
- activesupport (3.2.11)
+ activesupport (3.2.12)
i18n (~> 0.6)
multi_json (~> 1.0)
- addressable (2.3.2)
- amq-client (0.9.11)
- amq-protocol (>= 1.0.1)
+ addressable (2.3.3)
+ amq-client (0.9.12)
+ amq-protocol (>= 1.2.0)
eventmachine
- amq-protocol (1.1.0)
- amqp (0.9.8)
- amq-client (~> 0.9.5)
- amq-protocol (>= 0.9.4)
+ amq-protocol (1.2.0)
+ amqp (0.9.9)
+ amq-client (~> 0.9.12)
+ amq-protocol (~> 1.2.0)
eventmachine
antlr3 (1.8.12)
builder (3.1.4)
@@ -56,22 +56,22 @@ GEM
diff-lcs (1.1.3)
eventmachine (1.0.0)
eventmachine (1.0.0-java)
- gherkin (2.11.5)
- json (>= 1.4.6)
- gherkin (2.11.5-java)
- json (>= 1.4.6)
- hashie (1.2.0)
+ gherkin (2.11.6)
+ json (>= 1.7.6)
+ gherkin (2.11.6-java)
+ json (>= 1.7.6)
+ hashie (2.0.0)
highline (1.6.15)
httparty (0.10.2)
multi_json (~> 1.0)
multi_xml (>= 0.5.2)
i18n (0.6.1)
- jruby-jars (1.7.2)
+ jruby-jars (1.7.3)
jruby-rack (1.1.13.1)
- json (1.7.6)
- json (1.7.6-java)
- multi_json (1.5.0)
- multi_xml (0.5.2)
+ json (1.7.7)
+ json (1.7.7-java)
+ multi_json (1.6.1)
+ multi_xml (0.5.3)
nokogiri (1.5.6)
nokogiri (1.5.6-java)
rake (10.0.3)
@@ -84,18 +84,19 @@ GEM
diff-lcs (~> 1.1.3)
rspec-http (0.10.0)
rspec (~> 2.0)
- rspec-mocks (2.12.1)
+ rspec-mocks (2.12.2)
+ rubygems-tasks (0.2.3)
rubyzip (0.9.9)
simplecov (0.7.1)
multi_json (~> 1.0)
simplecov-html (~> 0.7.1)
simplecov-html (0.7.1)
uuidtools (2.1.3)
- webmock (1.9.0)
+ webmock (1.9.3)
addressable (>= 2.2.7)
- crack (>= 0.1.7)
- yard (0.8.3)
- yard-cucumber (2.2.2)
+ crack (>= 0.3.2)
+ yard (0.8.4.1)
+ yard-cucumber (2.2.3)
cucumber (>= 0.7.5)
gherkin (>= 2.2.9)
yard (>= 0.8.1)
@@ -114,6 +115,7 @@ DEPENDENCIES
rake
rspec
rspec-http
+ rubygems-tasks
simplecov
vcr!
warbler!
View
6 README.md
@@ -150,10 +150,14 @@ For keystone auth use
#### DSL
In your scripts, you can use the OCCI client DSL.
-To include the DSL definitions in your script use
+To include the DSL definitions in a script use
extend Occi::Api::Dsl
+To include the DSL definitions in a class use
+
+ include Occi::Api:Dsl
+
To connect to an OCCI endpoint/server (e.g. running on http://localhost:3300/ )
connect(:http, 'http://localhost:3300',auth||=nil)
View
10 Rakefile
@@ -1,7 +1,11 @@
-require 'bundler'
-Bundler::GemHelper.install_tasks
+require 'rubygems/tasks'
-task :default => 'rcov:all'
+task :default => 'test'
+
+desc "Run all tests; includes rspec, cucumber and coverage reports"
+task :test => 'rcov:all'
+
+Gem::Tasks.new(:build => {:tar => true, :zip => true}, :sign => {:checksum => true, :pgp => true})
namespace :rcov do
View
69 bin/occi
@@ -41,28 +41,6 @@ output = Occi::Bin::ResourceOutputFactory.new options.output_format
Occi::Log.info "Starting OCCI client ..."
Occi::Log.debug "Options: #{options}"
-# TODO: this part isn't necessary, just annoying
-# to make the interactive mode completely self-sufficient
-# ask for endpoint and auth method (provide defaults)
-#if options.interactive
-# Occi::Log.debug "Checking for endpoint and auth changes ..."
-#
-# options.endpoint = ask("What endpoint should I use? ") {
-# |q| q.default = options.endpoint
-# }
-#
-# # separate menus
-# say "\n"
-#
-# choose do |menu|
-# menu.prompt = "Which auth method should I use? "
-#
-# Occi::Bin::OcciOpts::AUTH_METHODS.each do |auth_m|
-# menu.choice(auth_m) { options.auth[:type] = auth_m.to_s }
-# end
-# end
-#end
-
# running with an empty password, we should ask the user for one
# if auth method is not "none"
if options.auth[:password].nil? || options.auth[:user_cert_password].nil? || options.auth[:token].nil?
@@ -118,7 +96,7 @@ end
if options.dump_model
if !model.respond_to? :instance_variables
- puts "Your Ruby doesn't support 'instance_variables' calls!"
+ Occi::Log.error "Your Ruby doesn't support 'instance_variables' calls!"
exit!
end
@@ -261,17 +239,19 @@ begin
# if the user didn't choose "Back", ask for details
# TODO: currently only COMPUTE is supported
if options.action == :create
- options.resource_title = ask("What name should I give to the new resource? ")
+ options.attributes = {} if options.attributes.nil?
+
+ options.attributes[:title] = ask("What name should I give to the new resource? ")
number_of_mixins = ask("How many mixins do you wish me to mix into this resource? ",
Integer) { |q| q.in = 0..2 }
- options.mixin = {}
+ options.mixins = {}
(1..number_of_mixins).each do |mixin_number|
mixin = ask("What mixin should I mix in? ") { |q| q.validate = /\A\w+#\w+\Z/ }
parts = mixin.split("#")
- options.mixin[parts[0]] = [] if options.mixin[parts[0]].nil?
- options.mixin[parts[0]] << parts[1]
+ options.mixins[parts[0]] = [] if options.mixins[parts[0]].nil?
+ options.mixins[parts[0]] << parts[1]
end
end
}
@@ -340,40 +320,15 @@ begin
# call the appropriate helper and then format its output
case options.action
when :list
- found = helper_list options
-
- valid = Occi::Bin::ResourceOutputFactory.allowed_resource_types.include? options.resource.to_sym
- Occi::Log.error "Not printing, the resource type is not supported!" unless valid
-
- puts output.format(found, :locations, options.resource.to_sym) if valid
+ helper_list options, output
when :describe
- found = helper_describe options
-
- if options.resource.start_with? options.endpoint
- resource_type = options.resource.split("/")[3].to_sym
- elsif mixin_types.include? options.resource.split('#').first
- resource_type = options.resource.split('#').first.to_sym
- else
- resource_type = options.resource.to_sym
- end
-
- valid = Occi::Bin::ResourceOutputFactory.allowed_resource_types.include? resource_type
- Occi::Log.error "Not printing, the resource type is not supported!" unless valid
-
- puts output.format(found, :resources, resource_type) if valid
+ helper_describe options, output
when :create
- location = helper_create options
- puts location
+ helper_create options, output
when :delete
- result = helper_delete options
-
- if result
- puts "Resource #{options.resource} successfully removed!"
- else
- puts "Failed to remove resource #{options.resource}!"
- end
+ helper_delete options, output
when :trigger
- helper_trigger options
+ helper_trigger options, output
when :refresh
refresh
when :skip
View
7 ext/mkrf_conf.rb
@@ -5,7 +5,8 @@
begin
Gem::Command.build_args = ARGV
rescue NoMethodError
- # do nothing
+ # do nothing but warn the user
+ warn "Gem::Command doesn't have a method named 'build_args'!"
end
if defined? RUBY_PLATFORM && RUBY_PLATFORM == "java"
@@ -14,8 +15,8 @@
begin
inst.install "jruby-openssl" if ((defined? JRUBY_VERSION) && (JRUBY_VERSION.split('.')[1].to_i < 7))
rescue
- # installation failed
- exit(1)
+ warn "Gem::DependencyInstaller failed to install 'jruby-openssl'!"
+ exit
end
end
View
281 lib/occi/api/client/client_http.rb
@@ -29,46 +29,46 @@ class ClientHttp
# hash mapping HTTP response codes to human-readable messages
HTTP_CODES = {
- "100" => "Continue",
- "101" => "Switching Protocols",
- "200" => "OK",
- "201" => "Created",
- "202" => "Accepted",
- "203" => "Non-Authoritative Information",
- "204" => "No Content",
- "205" => "Reset Content",
- "206" => "Partial Content",
- "300" => "Multiple Choices",
- "301" => "Moved Permanently",
- "302" => "Found",
- "303" => "See Other",
- "304" => "Not Modified",
- "305" => "Use Proxy",
- "307" => "Temporary Redirect",
- "400" => "Bad Request",
- "401" => "Unauthorized",
- "402" => "Payment Required",
- "403" => "Forbidden",
- "404" => "Not Found",
- "405" => "Method Not Allowed",
- "406" => "Not Acceptable",
- "407" => "Proxy Authentication Required",
- "408" => "Request Time-out",
- "409" => "Conflict",
- "410" => "Gone",
- "411" => "Length Required",
- "412" => "Precondition Failed",
- "413" => "Request Entity Too Large",
- "414" => "Request-URI Too Large",
- "415" => "Unsupported Media Type",
- "416" => "Requested range not satisfiable",
- "417" => "Expectation Failed",
- "500" => "Internal Server Error",
- "501" => "Not Implemented",
- "502" => "Bad Gateway",
- "503" => "Service Unavailable",
- "504" => "Gateway Time-out",
- "505" => "HTTP Version not supported"
+ "100" => "Continue",
+ "101" => "Switching Protocols",
+ "200" => "OK",
+ "201" => "Created",
+ "202" => "Accepted",
+ "203" => "Non-Authoritative Information",
+ "204" => "No Content",
+ "205" => "Reset Content",
+ "206" => "Partial Content",
+ "300" => "Multiple Choices",
+ "301" => "Moved Permanently",
+ "302" => "Found",
+ "303" => "See Other",
+ "304" => "Not Modified",
+ "305" => "Use Proxy",
+ "307" => "Temporary Redirect",
+ "400" => "Bad Request",
+ "401" => "Unauthorized",
+ "402" => "Payment Required",
+ "403" => "Forbidden",
+ "404" => "Not Found",
+ "405" => "Method Not Allowed",
+ "406" => "Not Acceptable",
+ "407" => "Proxy Authentication Required",
+ "408" => "Request Time-out",
+ "409" => "Conflict",
+ "410" => "Gone",
+ "411" => "Length Required",
+ "412" => "Precondition Failed",
+ "413" => "Request Entity Too Large",
+ "414" => "Request-URI Too Large",
+ "415" => "Unsupported Media Type",
+ "416" => "Requested range not satisfiable",
+ "417" => "Expectation Failed",
+ "500" => "Internal Server Error",
+ "501" => "Not Implemented",
+ "502" => "Bad Gateway",
+ "503" => "Service Unavailable",
+ "504" => "Gateway Time-out",
+ "505" => "HTTP Version not supported"
}
# Initializes client data structures and retrieves OCCI model
@@ -84,8 +84,8 @@ class ClientHttp
# @param [String] media type identifier
# @return [Occi::Api::Client::ClientHttp] client instance
def initialize(endpoint = "http://localhost:3000/", auth_options = {:type => "none"},
- log_options = {:out => STDERR, :level => Occi::Log::WARN, :logger => nil},
- auto_connect = true, media_type = nil)
+ log_options = {:out => STDERR, :level => Occi::Log::WARN, :logger => nil},
+ auto_connect = true, media_type = nil)
# set Occi::Log
set_logger log_options
@@ -136,7 +136,7 @@ def get_resource(resource_type)
elsif @model.kinds.select { |kind| kind.term == resource_type }.any?
# we got a resource type name
Occi::Core::Resource.new @model.kinds.select {
- |kind| kind.term == resource_type
+ |kind| kind.term == resource_type
}.first.type_identifier
else
raise "Unknown resource type! [#{resource_type}]"
@@ -265,20 +265,20 @@ def find_mixin(name, type = nil, describe = false)
if type
# get the first match from either os_tpls or resource_tpls
case
- when type == "os_tpl"
- get_os_templates.select { |mixin| mixin.term == name }.first
- when type == "resource_tpl"
- get_resource_templates.select { |template| template.term == name }.first
- else
- nil
+ when type == "os_tpl"
+ get_os_templates.select { |mixin| mixin.term == name }.first
+ when type == "resource_tpl"
+ get_resource_templates.select { |template| template.term == name }.first
+ else
+ nil
end
else
# try in os_tpls first
found = get_os_templates.select { |os| os.term == name }.first
# then try in resource_tpls
found = get_resource_templates.select {
- |template| template.term == name
+ |template| template.term == name
}.first unless found
found
@@ -290,12 +290,12 @@ def find_mixin(name, type = nil, describe = false)
if type
# return the first match with the selected type
@mixins[type.to_sym].select {
- |mixin| mixin.to_s.reverse.start_with? name.reverse
+ |mixin| mixin.to_s.reverse.start_with? name.reverse
}.first
else
# there is no type preference, return first global match
@mixins.flatten(2).select {
- |mixin| mixin.to_s.reverse.start_with? name.reverse
+ |mixin| mixin.to_s.reverse.start_with? name.reverse
}.first
end
end
@@ -344,7 +344,7 @@ def get_mixins(type = nil)
#
# @return [Array<String>] list of available mixin types
def get_mixin_types
- @mixins.keys.map! { |k| k.to_s }
+ @mixins.keys.map { |k| k.to_s }
end
# Retrieves available mixin type identifiers.
@@ -386,9 +386,9 @@ def list(resource_type_identifier=nil)
if resource_type_identifier
# convert type to type identifier
resource_type_identifier = @model.kinds.select {
- |kind| kind.term == resource_type_identifier
+ |kind| kind.term == resource_type_identifier
}.first.type_identifier if @model.kinds.select {
- |kind| kind.term == resource_type_identifier
+ |kind| kind.term == resource_type_identifier
}.any?
# check some basic pre-conditions
@@ -431,9 +431,9 @@ def describe(resource_type_identifier=nil)
# convert type to type identifier
resource_type_identifier = @model.kinds.select {
- |kind| kind.term == resource_type_identifier
+ |kind| kind.term == resource_type_identifier
}.first.type_identifier if @model.kinds.select {
- |kind| kind.term == resource_type_identifier
+ |kind| kind.term == resource_type_identifier
}.any?
# check some basic pre-conditions
@@ -452,7 +452,7 @@ def describe(resource_type_identifier=nil)
locations.each do |location|
descriptions << get(sanitize_resource_link(location))
end
- elsif resource_type_identifier.start_with? @endpoint
+ elsif resource_type_identifier.start_with?(@endpoint) || resource_type_identifier.start_with?('/')
# we got resource link
# make the request
descriptions << get(sanitize_resource_link(resource_type_identifier))
@@ -570,7 +570,7 @@ def trigger(resource_type_identifier, action)
# TODO: not tested
if @model.kinds.select { |kind| kind.term == resource_type }.any?
type_identifier = @model.kinds.select {
- |kind| kind.term == resource_type_identifier
+ |kind| kind.term == resource_type_identifier
}.first.type_identifier
location = @model.get_by_id(type_identifier).location
@@ -635,40 +635,40 @@ def change_auth(auth_options)
@auth_options = auth_options
case @auth_options[:type]
- when "basic"
- # set up basic auth
- raise ArgumentError, "Missing required options 'username' and 'password' for basic auth!" unless @auth_options[:username] and @auth_options[:password]
- self.class.basic_auth @auth_options[:username], @auth_options[:password]
- when "digest"
- # set up digest auth
- raise ArgumentError, "Missing required options 'username' and 'password' for digest auth!" unless @auth_options[:username] and @auth_options[:password]
- self.class.digest_auth @auth_options[:username], @auth_options[:password]
- when "x509"
- # set up pem and optionally pem_password and ssl_ca_path
- raise ArgumentError, "Missing required option 'user_cert' for x509 auth!" unless @auth_options[:user_cert]
- raise ArgumentError, "The file specified in 'user_cert' does not exist!" unless File.exists? @auth_options[:user_cert]
-
- # handle PKCS#12 credentials before passing them
- # to httparty
- if /\A(.)+\.p12\z/ =~ @auth_options[:user_cert]
- self.class.pem AuthnUtils.extract_pem_from_pkcs12(@auth_options[:user_cert], @auth_options[:user_cert_password]), ''
- else
- # httparty will handle ordinary PEM formatted credentials
- # TODO: Issue #49, check PEM credentials in jRuby
- self.class.pem File.open(@auth_options[:user_cert], 'rb').read, @auth_options[:user_cert_password]
- end
-
- self.class.ssl_ca_path @auth_options[:ca_path] unless @auth_options[:ca_path].nil?
- self.class.ssl_ca_file @auth_options[:ca_file] unless @auth_options[:ca_file].nil?
- self.class.ssl_extra_chain_cert AuthnUtils.certs_to_file_ary(@auth_options[:proxy_ca]) unless @auth_options[:proxy_ca].nil?
- when "keystone"
- # set up OpenStack Keystone token based auth
- raise ArgumentError, "Missing required option 'token' for OpenStack Keystone auth!" unless @auth_options[:token]
- self.class.headers['X-Auth-Token'] = @auth_options[:token]
- when "none", nil
- # do nothing
+ when "basic"
+ # set up basic auth
+ raise ArgumentError, "Missing required options 'username' and 'password' for basic auth!" unless @auth_options[:username] and @auth_options[:password]
+ self.class.basic_auth @auth_options[:username], @auth_options[:password]
+ when "digest"
+ # set up digest auth
+ raise ArgumentError, "Missing required options 'username' and 'password' for digest auth!" unless @auth_options[:username] and @auth_options[:password]
+ self.class.digest_auth @auth_options[:username], @auth_options[:password]
+ when "x509"
+ # set up pem and optionally pem_password and ssl_ca_path
+ raise ArgumentError, "Missing required option 'user_cert' for x509 auth!" unless @auth_options[:user_cert]
+ raise ArgumentError, "The file specified in 'user_cert' does not exist!" unless File.exists? @auth_options[:user_cert]
+
+ # handle PKCS#12 credentials before passing them
+ # to httparty
+ if /\A(.)+\.p12\z/ =~ @auth_options[:user_cert]
+ self.class.pem AuthnUtils.extract_pem_from_pkcs12(@auth_options[:user_cert], @auth_options[:user_cert_password]), ''
else
- raise ArgumentError, "Unknown AUTH method [#{@auth_options[:type]}]!"
+ # httparty will handle ordinary PEM formatted credentials
+ # TODO: Issue #49, check PEM credentials in jRuby
+ self.class.pem File.open(@auth_options[:user_cert], 'rb').read, @auth_options[:user_cert_password]
+ end
+
+ self.class.ssl_ca_path @auth_options[:ca_path] unless @auth_options[:ca_path].nil?
+ self.class.ssl_ca_file @auth_options[:ca_file] unless @auth_options[:ca_file].nil?
+ self.class.ssl_extra_chain_cert AuthnUtils.certs_to_file_ary(@auth_options[:proxy_ca]) unless @auth_options[:proxy_ca].nil?
+ when "keystone"
+ # set up OpenStack Keystone token based auth
+ raise ArgumentError, "Missing required option 'token' for OpenStack Keystone auth!" unless @auth_options[:token]
+ self.class.headers['X-Auth-Token'] = @auth_options[:token]
+ when "none", nil
+ # do nothing
+ else
+ raise ArgumentError, "Unknown AUTH method [#{@auth_options[:type]}]!"
end
end
@@ -684,7 +684,7 @@ def change_auth(auth_options)
# @return [Occi::Collection] parsed result of the request
def get(path='', filter=nil)
# remove the leading slash
- path.gsub!(/\A\//, '')
+ path = path.gsub(/\A\//, '')
response = if filter
categories = filter.categories.collect { |category| category.to_text }.join(',')
@@ -737,39 +737,39 @@ def get(path='', filter=nil)
# @return [String] URI location
def post(path, collection)
# remove the leading slash
- path.gsub!(/\A\//, '')
+ path = path.gsub(/\A\//, '')
headers = self.class.headers.clone
headers['Content-Type'] = @media_type
response = case @media_type
- when 'application/occi+json'
- self.class.post(@endpoint + path,
- :body => collection.to_json,
- :headers => headers)
- when 'text/occi'
- self.class.post(@endpoint + path,
- :headers => collection.to_header.merge(headers))
- else
- self.class.post(@endpoint + path,
- :body => collection.to_text,
- :headers => headers)
+ when 'application/occi+json'
+ self.class.post(@endpoint + path,
+ :body => collection.to_json,
+ :headers => headers)
+ when 'text/occi'
+ self.class.post(@endpoint + path,
+ :headers => collection.to_header.merge(headers))
+ else
+ self.class.post(@endpoint + path,
+ :body => collection.to_text,
+ :headers => headers)
end
response_msg = response_message response
case response.code
- when 200
- collection = Occi::Parser.parse(response.header["content-type"].split(";").first, response)
- if collection.empty?
- Occi::Parser.locations(response.header["content-type"].split(";").first, response.body, response.header).first
- else
- collection.resources.first.location if collection.resources.first
- end
- when 201
+ when 200
+ collection = Occi::Parser.parse(response.header["content-type"].split(";").first, response)
+ if collection.empty?
Occi::Parser.locations(response.header["content-type"].split(";").first, response.body, response.header).first
else
- raise "HTTP POST failed! #{response_msg}"
+ collection.resources.first.location if collection.resources.first
+ end
+ when 201
+ Occi::Parser.locations(response.header["content-type"].split(";").first, response.body, response.header).first
+ else
+ raise "HTTP POST failed! #{response_msg}"
end
end
@@ -783,32 +783,32 @@ def post(path, collection)
# @return [Occi::Collection] parsed result of the request
def put(path, collection)
# remove the leading slash
- path.gsub!(/\A\//, '')
+ path = path.gsub(/\A\//, '')
headers = self.class.headers.clone
headers['Content-Type'] = @media_type
response = case @media_type
- when 'application/occi+json'
- self.class.post(@endpoint + path,
- :body => collection.to_json,
- :headers => headers)
- when 'text/occi'
- self.class.post(@endpoint + path,
- :headers => collection.to_header.merge(headers))
- else
- self.class.post(@endpoint + path,
- :body => collection.to_text,
- :headers => headers)
+ when 'application/occi+json'
+ self.class.post(@endpoint + path,
+ :body => collection.to_json,
+ :headers => headers)
+ when 'text/occi'
+ self.class.post(@endpoint + path,
+ :headers => collection.to_header.merge(headers))
+ else
+ self.class.post(@endpoint + path,
+ :body => collection.to_text,
+ :headers => headers)
end
response_msg = response_message response
case response.code
- when 200, 201
- Occi::Parser.parse(response.header["content-type"].split(";").first, response)
- else
- raise "HTTP POST failed! #{response_msg}"
+ when 200, 201
+ Occi::Parser.parse(response.header["content-type"].split(";").first, response)
+ else
+ raise "HTTP POST failed! #{response_msg}"
end
end
@@ -822,7 +822,7 @@ def put(path, collection)
# @return [Boolean] status
def del(path, filter=nil)
# remove the leading slash
- path.gsub!(/\A\//, '')
+ path = path.gsub(/\A\//, '')
response = self.class.delete(@endpoint + path)
@@ -882,10 +882,15 @@ def prepare_endpoint(endpoint)
# @example
# sanitize_resource_link "http://localhost:3300/compute/35ad4f45gsf-gsfg524s6gsfg-sfgsf4gsfg"
# # => "/compute/35ad4f45gsf-gsfg524s6gsfg-sfgsf4gsfg"
+ # sanitize_resource_link "/compute/35ad4f45gsf-gsfg524s6gsfg-sfgsf4gsfg"
+ # # => "/compute/35ad4f45gsf-gsfg524s6gsfg-sfgsf4gsfg"
#
# @param [String] string containing the full resource link
# @return [String] extracted path, with a leading slash
def sanitize_resource_link(resource_link)
+ # everything starting with '/' is considered to be a resource path
+ return resource_link if resource_link.start_with? '/'
+
raise "Resource link #{resource_link} is not valid!" unless resource_link.start_with? @endpoint
resource_link.gsub @endpoint, '/'
@@ -908,7 +913,7 @@ def path_for_resource_type(resource_type_identifier)
if kinds.any?
#we got an type identifier
path = "/" + kinds.first.type_identifier.split('#').last + "/"
- elsif resource_type_identifier.start_with? @endpoint
+ elsif resource_type_identifier.start_with?(@endpoint) || resource_type_identifier.start_with?('/')
#we got an resource link
path = sanitize_resource_link(resource_type_identifier)
else
@@ -930,8 +935,8 @@ def set_model
@model = Occi::Model.new(model)
@mixins = {
- :os_tpl => [],
- :resource_tpl => []
+ :os_tpl => [],
+ :resource_tpl => []
}
#
@@ -976,10 +981,10 @@ def set_media_type
media_types = self.class.head(@endpoint).headers['accept']
Occi::Log.debug("Available media types: #{media_types}")
@media_type = case media_types
- when /application\/occi\+json/
- 'application/occi+json'
- else
- 'text/plain'
+ when /application\/occi\+json/
+ 'application/occi+json'
+ else
+ 'text/plain'
end
end
View
78 lib/occi/bin/helpers.rb
@@ -1,6 +1,6 @@
# a bunch of OCCI client helpers for bin/occi
-def helper_list(options)
+def helper_list(options, output = nil)
found = []
if resource_types.include? options.resource
@@ -10,17 +10,23 @@ def helper_list(options)
Occi::Log.debug "#{options.resource} is a mixin type."
found = mixins options.resource
else
- Occi::Log.debug "I have no idea what #{options.resource} is ..."
- puts "Unknown resource #{options.resource}, there is nothing to list here!"
+ Occi::Log.warn "I have no idea what #{options.resource} is ..."
+ raise "Unknown resource #{options.resource}, there is nothing to list here!"
end
- found
+ return found if output.nil?
+
+ if Occi::Bin::ResourceOutputFactory.allowed_resource_types.include? options.resource.to_sym
+ puts output.format(found, :locations, options.resource.to_sym)
+ else
+ Occi::Log.warn "Not printing, the resource type is not supported!"
+ end
end
-def helper_describe(options)
+def helper_describe(options, output = nil)
found = []
- if resource_types.include? options.resource or options.resource.start_with? options.endpoint
+ if resource_types.include?(options.resource) || options.resource.start_with?(options.endpoint) || options.resource.start_with?('/')
Occi::Log.debug "#{options.resource} is a resource type or an actual resource."
found = describe(options.resource)
@@ -37,15 +43,37 @@ def helper_describe(options)
mxn_type,mxn = options.resource.split('#')
found << mixin(mxn, mxn_type, true)
else
- Occi::Log.debug "I have no idea what #{options.resource} is ..."
+ Occi::Log.warn "I have no idea what #{options.resource} is ..."
+ raise "Unknown resource #{options.resource}, there is nothing to describe here!"
+ end
- puts "Unknown resource #{options.resource}, there is nothing to describe here!"
+ return found if output.nil?
+
+ if options.resource.start_with? options.endpoint
+ # resource contains full endpoint URI
+ # e.g., http://localhost:3300/network/adfgadf-daf5a6df4afadf-adfad65f4ad
+ resource_type = options.resource.split('/')[3].to_sym
+ elsif options.resource.start_with? '/'
+ # resource contains a path relative to endpoint URI
+ # e.g., /network/adfgadf-daf5a6df4afadf-adfad65f4ad
+ resource_type = options.resource.split('/')[1].to_sym
+ elsif mixin_types.include? options.resource.split('#').first
+ # resource contains a mixin with a type
+ # e.g., os_tpl#debian6
+ resource_type = options.resource.split('#').first.to_sym
+ else
+ # resource probably contains RAW resource_type
+ resource_type = options.resource.to_sym
end
- found
+ if Occi::Bin::ResourceOutputFactory.allowed_resource_types.include? resource_type
+ puts output.format(found, :resources, resource_type)
+ else
+ Occi::Log.warn "Not printing, the resource type [#{resource_type.to_s}] is not supported!"
+ end
end
-def helper_create(options)
+def helper_create(options, output = nil)
location = nil
if resource_types.include? options.resource
@@ -55,11 +83,11 @@ def helper_create(options)
res = resource options.resource
Occi::Log.debug "Creating #{options.resource}:\n#{res.inspect}"
- Occi::Log.debug "with mixins:#{options.mixin}"
+ Occi::Log.debug "with mixins:#{options.mixins}"
- options.mixin.keys.each do |type|
+ options.mixins.keys.each do |type|
Occi::Log.debug "Adding mixins of type #{type} to #{options.resource}"
- options.mixin[type].each do |name|
+ options.mixins[type].each do |name|
mxn = mixin name, type
raise "Unknown mixin #{type}##{name}, stopping here!" if mxn.nil?
@@ -69,23 +97,31 @@ def helper_create(options)
end
#TODO: set other attributes
- res.title = options.resource_title
+ res.title = options.attributes[:title]
Occi::Log.debug "Creating #{options.resource}:\n#{res.inspect}"
location = create res
else
- Occi::Log.debug "I have no idea what #{options.resource} is ..."
- puts "Unknown resource #{options.resource}, there is nothing to create here!"
+ Occi::Log.warn "I have no idea what #{options.resource} is ..."
+ raise "Unknown resource #{options.resource}, there is nothing to create here!"
end
- location
+ return location if output.nil?
+
+ puts location
end
-def helper_delete(options)
- delete options.resource
+def helper_delete(options, output = nil)
+ if delete(options.resource)
+ Occi::Log.info "Resource #{options.resource} successfully removed!"
+ else
+ raise "Failed to remove resource #{options.resource}!"
+ end
+
+ true
end
-def helper_trigger(options)
+def helper_trigger(options, output = nil)
raise "Not yet implemented!"
-end
+end
View
215 lib/occi/bin/occi_opts.rb
@@ -1,5 +1,7 @@
require 'ostruct'
require 'optparse'
+require 'uri'
+
require 'occi/bin/resource_output_factory'
module Occi
@@ -12,7 +14,9 @@ class OcciOpts
ACTIONS = [:list, :describe, :create, :delete, :trigger].freeze
LOG_OUTPUTS = [:stdout, :stderr].freeze
- def self.parse(args)
+ def self.parse(args, test_env = false)
+
+ @@quiet = test_env
options = OpenStruct.new
@@ -39,13 +43,31 @@ def self.parse(args)
options.auth[:proxy_ca] = nil
options.output_format = :plain
+
+ options.mixins = nil
+ options.links = nil
+ options.attributes = nil
+ options.context_vars = nil
# TODO: change media type back to occi+json after the rOCCI-server update
#options.media_type = "application/occi+json"
options.media_type = "text/plain,text/occi"
opts = OptionParser.new do |opts|
- opts.banner = "Usage: occi [OPTIONS]"
+ opts.banner = %{Usage: occi [OPTIONS]
+
+Examples:
+ occi --interactive --endpoint https://localhost:3300/ --auth x509
+
+ occi --endpoint https://localhost:3300/ --action list --resource os_tpl --auth x509
+
+ occi --endpoint https://localhost:3300/ --action list --resource resource_tpl --auth x509
+
+ occi --endpoint https://localhost:3300/ --action describe --resource os_tpl#debian6 --auth x509
+
+ occi --endpoint https://localhost:3300/ --action create --resource compute --mixin os_tpl#debian6 --mixin resource_tpl#small --attributes title="My rOCCI VM" --auth x509
+
+ occi --endpoint https://localhost:3300/ --action delete --resource /compute/65sd4f654sf65g4-s5fg65sfg465sfg-sf65g46sf5g4sdfg --auth x509}
opts.separator ""
opts.separator "Options:"
@@ -60,6 +82,9 @@ def self.parse(args)
"--endpoint URI",
String,
"OCCI server URI, defaults to '#{options.endpoint}'") do |endpoint|
+ # Do some basic validation of the endpoint URI
+ URI(endpoint)
+
options.endpoint = endpoint
end
@@ -80,36 +105,56 @@ def self.parse(args)
opts.on("-p",
"--password PASSWORD",
String,
- "Password for basic, digest and x509 authentication or an auth. token for KeyStone") do |password|
+ "Password for basic, digest and x509 authentication or an auth. token from OS Keystone") do |password|
options.auth[:password] = password
options.auth[:user_cert_password] = password
options.auth[:token] = password
end
opts.on("-c",
- "--ca-path PATH", String, "Path to CA certificates directory, defaults to '#{options.auth[:ca_path]}'") do |ca_path|
+ "--ca-path PATH",
+ String,
+ "Path to CA certificates directory, defaults to '#{options.auth[:ca_path]}'") do |ca_path|
+ raise ArgumentError, "Path specified in --ca-path is not a directory!" unless File.directory? ca_path
+ raise ArgumentError, "Path specified in --ca-path is not readable!" unless File.readable? ca_path
+
options.auth[:ca_path] = ca_path
end
opts.on("-f",
- "--ca-file PATH", String, "Path to CA certificates in a file") do |ca_file|
+ "--ca-file PATH",
+ String,
+ "Path to CA certificates in a file") do |ca_file|
+ raise ArgumentError, "File specified in --ca-file is not a file!" unless File.file? ca_file
+ raise ArgumentError, "File specified in --ca-file is not readable!" unless File.readable? ca_file
+
options.auth[:ca_file] = ca_file
end
opts.on("-F",
- "--filter CATEGORY", String, "Category type identifier to filter categories from model, must be used together with the -m option") do |filter|
+ "--filter CATEGORY",
+ String,
+ "Category type identifier to filter categories from model, must be used together with the -m option") do |filter|
options.filter = filter
end
opts.on("-x",
- "--user-cred PATH",
+ "--user-cred FILE",
String,
"Path to user's x509 credentials, defaults to '#{options.auth[:user_cert]}'") do |user_cred|
+ raise ArgumentError, "File specified in --user-cred is not a file!" unless File.file? user_cred
+ raise ArgumentError, "File specified in --user-cred is not readable!" unless File.readable? user_cred
+
options.auth[:user_cert] = user_cred
end
opts.on("-X",
- "--proxy-ca PATH", String, "Path to a file with GSI proxy's CA certificate(s)") do |proxy_ca|
+ "--proxy-ca FILE",
+ String,
+ "Path to a file with GSI proxy's CA certificate(s)") do |proxy_ca|
+ raise ArgumentError, "File specified in --proxy-ca is not a file!" unless File.file? proxy_ca
+ raise ArgumentError, "File specified in --proxy-ca is not readable!" unless File.readable? proxy_ca
+
options.auth[:proxy_ca] = proxy_ca
end
@@ -128,10 +173,31 @@ def self.parse(args)
end
opts.on("-t",
- "--resource-title TITLE",
- String,
- "Resource title for new resources") do |resource_title|
- options.resource_title = resource_title
+ "--attributes ATTRS",
+ Array,
+ "Comma separated attributes for new resources such as title=\"Name\", required") do |attributes|
+ options.attributes = {}
+
+ attributes.each do |attribute|
+ ary = /^(.+?)=(.+)$/.match(attribute).to_a.drop 1
+ raise ArgumentError, "Attributes must always contain ATTR=VALUE pairs!" if ary.length != 2
+
+ options.attributes[ary[0].to_sym] = ary[1]
+ end
+ end
+
+ opts.on("-T",
+ "--context CTX_VARS",
+ Array,
+ "Comma separated context variables for new compute resources such as SSH_KEY=\"ssh-rsa dfsdf...adfdf== user@localhost\"") do |context|
+ options.context_vars = {}
+
+ context.each do |ctx|
+ ary = /^(.+?)=(.+)$/.match(ctx).to_a.drop 1
+ raise ArgumentError, "Context variables must always contain ATTR=VALUE pairs!" if ary.length != 2
+
+ options.context_vars[ary[0].to_sym] = ary[1]
+ end
end
opts.on("-a",
@@ -149,9 +215,21 @@ def self.parse(args)
raise "Unknown mixin format! Use TYPE#NAME!" unless parts.length == 2
- options.mixin = {} if options.mixin.nil?
- options.mixin[parts[0]] = [] if options.mixin[parts[0]].nil?
- options.mixin[parts[0]] << parts[1]
+ options.mixins = {} if options.mixins.nil?
+ options.mixins[parts[0]] = [] if options.mixins[parts[0]].nil?
+ options.mixins[parts[0]] << parts[1]
+ end
+
+ opts.on("-j",
+ "--link URI",
+ String,
+ "Link specified resource to the resource being created, only for action CREATE and resource COMPUTE") do |link|
+ link_relative_path = URI(link).path
+
+ raise ArgumentError, "Specified link URI is not valid!" unless link_relative_path.start_with? '/'
+
+ options.links = [] if options.links.nil?
+ options.links << link_relative_path
end
opts.on("-g",
@@ -198,68 +276,111 @@ def self.parse(args)
opts.on_tail("-h",
"--help",
"Show this message") do
- puts opts
- exit!(true)
+ if @@quiet
+ exit true
+ else
+ puts opts
+ exit! true
+ end
end
opts.on_tail("-v",
"--version",
"Show version") do
- puts Occi::VERSION
- exit!(true)
+ if @@quiet
+ exit true
+ else
+ puts Occi::VERSION
+ exit! true
+ end
end
-
end
begin
opts.parse!(args)
rescue Exception => ex
- puts ex.message.capitalize
- puts opts
- exit!
+ if @@quiet
+ exit false
+ else
+ puts ex.message.capitalize
+ puts opts
+ exit!
+ end
end
- if options.interactive && options.dump_model
- puts "You cannot use '--dump-model' and '--interactive' at the same time!"
- puts opts
+ check_restrictions options, opts
+
+ options
+ end
+
+ private
- exit!
+ def self.check_restrictions(options, opts)
+ if options.interactive && options.dump_model
+ if @@quiet
+ exit false
+ else
+ puts "You cannot use '--dump-model' and '--interactive' at the same time!"
+ puts opts
+ exit!
+ end
end
if !options.dump_model && options.filter
- puts "You cannot use '--filter' without '--dump-model'!"
- puts opts
-
- exit!
+ if @@quiet
+ exit false
+ else
+ puts "You cannot use '--filter' without '--dump-model'!"
+ puts opts
+ exit!
+ end
end
- if !(options.interactive or options.dump_model)
- mandatory = []
+ return if options.interactive || options.dump_model
- if options.action == :trigger
- mandatory << :trigger_action
- end
+ mandatory = []
+
+ if options.action == :trigger
+ mandatory << :trigger_action
+ end
- if options.action == :create
- mandatory << :mixin << :resource_title
+ if options.action == :create
+ if !options.links.nil?
+ mandatory << :links
+ else
+ mandatory << :mixins
end
- mandatory.concat [:resource, :action]
-
- options_hash = options.marshal_dump
+ mandatory << :attributes
+ check_attrs = true
+ end
+
+ mandatory.concat [:resource, :action]
+
+ check_hash options, mandatory, opts
- missing = mandatory.select{ |param| options_hash[param].nil? }
- if not missing.empty?
+ if check_attrs
+ mandatory = [:title]
+ check_hash options.attributes, mandatory, opts
+ end
+ end
+
+ def self.check_hash(hash, mandatory, opts)
+ if !hash.is_a? Hash
+ hash = hash.marshal_dump
+ end
+
+ missing = mandatory.select{ |param| hash[param].nil? }
+ if !missing.empty?
+ if @@quiet
+ exit false
+ else
puts "Missing required arguments: #{missing.join(', ')}"
puts opts
-
exit!
end
end
-
- options
end
-
end
end
View
2 lib/occi/version.rb
@@ -1,3 +1,3 @@
module Occi
- VERSION = "3.0.0" unless defined?(::Occi::VERSION)
+ VERSION = "3.1.0.alpha.1" unless defined?(::Occi::VERSION)
end
View
2 occi.gemspec
@@ -39,7 +39,7 @@ Gem::Specification.new do |gem|
gem.add_development_dependency "yard-rspec"
gem.add_development_dependency "yard-cucumber"
gem.add_development_dependency "rspec-http"
- #gem.add_development_dependency "vcr"
+ gem.add_development_dependency "rubygems-tasks"
gem.add_development_dependency "webmock"
gem.required_ruby_version = ">= 1.8.7"
View
12 spec/occi/bin/helpers_spec.rb
@@ -0,0 +1,12 @@
+require 'rspec'
+require 'occi/bin/helpers'
+
+# module Occi
+# module Bin
+# describe Helpers do
+
+# it "does something"
+
+# end
+# end
+# end
View
60 spec/occi/bin/occi_opts_spec.rb
@@ -0,0 +1,60 @@
+require 'rspec'
+require 'occi/bin/occi_opts'
+
+module Occi
+ module Bin
+ describe OcciOpts do
+
+ it "terminates without arguments" do
+ expect{Occi::Bin::OcciOpts.parse [], true}.to raise_error(SystemExit)
+ end
+
+ it "terminates when it encounters an unknown argument" do
+ expect{Occi::Bin::OcciOpts.parse ["--non-existent", "fake"], true}.to raise_error(SystemExit)
+ end
+
+ it "parses minimal number of required arguments without errors" do
+ expect{Occi::Bin::OcciOpts.parse ["--resource", "compute", "--action", "list"], true}.not_to raise_error(SystemExit)
+ end
+
+ it "parses resource and action arguments without errors" do
+ begin
+ parsed = Occi::Bin::OcciOpts.parse ["--resource", "compute", "--action", "list"], true
+ rescue SystemExit
+ fail
+ end
+
+ parsed.should_not be_nil
+
+ parsed.resource.should_not be_nil
+ parsed.action.should_not be_nil
+
+ parsed.resource.should eq("compute")
+ parsed.action.should eq(:list)
+ end
+
+ it "shows version"
+
+ it "shows help"
+
+ it "doesn't accept unsupported authN methods"
+
+ it "doesn't accept an invalid URI as an endpoint"
+
+ it "doesn't accept an invalid URI as a resource"
+
+ it "doesn't accept unsupported actions"
+
+ it "doesn't allow create actions without attributes present"
+
+ it "doesn't allow create actions without either link(s) or mixin(s)"
+
+ it "doesn't allow create actions without attribute 'title' present"
+
+ it "doesn't accept malformed mixins"
+
+ it "doesn't accept malformed attributes"
+
+ end
+ end
+end
View
12 spec/occi/bin/resource_output_factory_spec.rb
@@ -0,0 +1,12 @@
+require 'rspec'
+require 'occi/bin/resource_output_factory'
+
+module Occi
+ module Bin
+ describe ResourceOutputFactory do
+
+ it "does something"
+
+ end
+ end
+end

0 comments on commit 5066c48

Please sign in to comment.
Something went wrong with that request. Please try again.