forked from ManageIQ/manageiq-api-client
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding support for subcollections and related subresources
- Dynamically driven by the "subcollections" exposed via OPTIONS /api/:collection - supports queries and actions Queries: miq.vms.find(166).tags.collect(&:name) miq.vms.find(166).tags.select(:categorization).collect(&:categorization) Subcollection Actions: miq.vms.find(166).tags.assign(:name => "/managed/location/ny") miq.vms.find(166).tags.assign([{:name => "/managed/location/chicago"}, {:name => "/managed/cc/001"}]) Subcollection resource actions: miq.vms.find(166).tags.find(32).unassign miq.vms.find(166).tags.where(:name => "/managed/location/*").collect(&:unassign) Fixes: ManageIQ#31 Fixes: ManageIQ#32
- Loading branch information
Showing
5 changed files
with
308 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
module ManageIQ | ||
module API | ||
class Client | ||
class Subcollection | ||
include ActionMixin | ||
include Enumerable | ||
include QueryRelation::Queryable | ||
|
||
ACTIONS_RETURNING_RESOURCES = %w(create query).freeze | ||
|
||
CUSTOM_INSPECT_EXCLUSIONS = [:@resource].freeze | ||
include CustomInspectMixin | ||
|
||
attr_reader :name | ||
attr_reader :href | ||
attr_reader :resource | ||
|
||
delegate :client, :to => :resource | ||
|
||
def initialize(name, resource) | ||
@name, @resource, @href = name.to_s, resource, "#{resource.href}/#{name}" | ||
clear_actions | ||
result_hash = client.get(href, :hide => "resources") | ||
fetch_actions(result_hash) | ||
end | ||
|
||
def each(&block) | ||
all.each(&block) | ||
end | ||
|
||
# find(#) returns the object | ||
# find([#]) returns an array of the object | ||
# find(#, #, ...) or find([#, #, ...]) returns an array of the objects | ||
def find(*args) | ||
request_array = args.size == 1 && args[0].kind_of?(Array) | ||
args = args.flatten | ||
case args.size | ||
when 0 | ||
raise "Couldn't find resource without an 'id'" | ||
when 1 | ||
res = limit(1).where(:id => args[0]).to_a | ||
raise "Couldn't find resource with 'id' #{args}" if res.blank? | ||
request_array ? res : res.first | ||
else | ||
raise "Multiple resource find is not supported" unless respond_to?(:query) | ||
query(args.collect { |id| { "id" => id } }) | ||
end | ||
end | ||
|
||
def find_by(args) | ||
limit(1).where(args).first | ||
end | ||
|
||
def pluck(*attrs) | ||
select(*attrs).to_a.pluck(*attrs) | ||
end | ||
|
||
def self.subclass(name) | ||
name = name.camelize | ||
|
||
if const_defined?(name, false) | ||
const_get(name, false) | ||
else | ||
const_set(name, Class.new(self)) | ||
end | ||
end | ||
|
||
def get(options = {}) | ||
options[:expand] = (String(options[:expand]).split(",") | %w(resources)).join(",") | ||
options[:filter] = Array(options[:filter]) if options[:filter].is_a?(String) | ||
result_hash = client.get(href, options) | ||
fetch_actions(result_hash) | ||
klass = ManageIQ::API::Client::Subresource.subclass(name) | ||
result_hash["resources"].collect do |resource_hash| | ||
klass.new(self, resource_hash) | ||
end | ||
end | ||
|
||
def search(mode, options) | ||
options[:limit] = 1 if mode == :first | ||
result = get(parameters_from_query_relation(options)) | ||
case mode | ||
when :first then result.first | ||
when :last then result.last | ||
when :all then result | ||
else raise "Invalid mode #{mode} specified for search" | ||
end | ||
end | ||
|
||
private | ||
|
||
def method_missing(sym, *args, &block) | ||
# get unless actions_present? | ||
if action_defined?(sym) | ||
exec_action(sym, *args, &block) | ||
else | ||
super | ||
end | ||
end | ||
|
||
def respond_to_missing?(sym, *_) | ||
# get unless actions_present? | ||
action_defined?(sym) || super | ||
end | ||
|
||
def parameters_from_query_relation(options) | ||
api_params = {} | ||
[:offset, :limit].each { |opt| api_params[opt] = options[opt] if options[opt] } | ||
api_params[:attributes] = options[:select].join(",") if options[:select].present? | ||
if options[:where] | ||
api_params[:filter] ||= [] | ||
api_params[:filter] += filters_from_query_relation("=", options[:where]) | ||
end | ||
if options[:not] | ||
api_params[:filter] ||= [] | ||
api_params[:filter] += filters_from_query_relation("!=", options[:not]) | ||
end | ||
if options[:order] | ||
order_parameters_from_query_relation(options[:order]).each { |param, value| api_params[param] = value } | ||
end | ||
api_params | ||
end | ||
|
||
def filters_from_query_relation(condition, option) | ||
filters = [] | ||
option.each do |attr, values| | ||
Array(values).each do |value| | ||
value = "'#{value}'" if value.kind_of?(String) && !value.match(/^(NULL|nil)$/i) | ||
filters << "#{attr}#{condition}#{value}" | ||
end | ||
end | ||
filters | ||
end | ||
|
||
def order_parameters_from_query_relation(option) | ||
query_relation_option = | ||
if option.kind_of?(Array) | ||
option.each_with_object({}) { |name, hash| hash[name] = "asc" } | ||
else | ||
option.dup | ||
end | ||
|
||
res_sort_by = [] | ||
res_sort_order = [] | ||
query_relation_option.each do |sort_attr, sort_order| | ||
res_sort_by << sort_attr | ||
sort_order = | ||
case sort_order | ||
when /^asc/i then "asc" | ||
when /^desc/i then "desc" | ||
else raise "Invalid sort order #{sort_order} specified for attribute #{sort_attr}" | ||
end | ||
res_sort_order << sort_order | ||
end | ||
{ :sort_by => res_sort_by.join(","), :sort_order => res_sort_order.join(",") } | ||
end | ||
|
||
def exec_action(name, *args, &block) | ||
action = find_action(name) | ||
body = action_body(action.name, *args, &block) | ||
bulk_request = body.key?("resources") | ||
res = client.send(action.method, URI(action.href)) { body } | ||
if ACTIONS_RETURNING_RESOURCES.include?(action.name) && res.key?("results") | ||
klass = ManageIQ::API::Client::Resource.subclass(self.name) | ||
res = results_to_objects(res["results"], klass) | ||
res = res[0] if !bulk_request && res.size == 1 | ||
else | ||
res = res["results"].collect { |result| action_result(result) } | ||
end | ||
res | ||
end | ||
|
||
def results_to_objects(results, klass) | ||
results.collect do |resource_hash| | ||
if ManageIQ::API::Client::ActionResult.an_action_result?(resource_hash) | ||
ManageIQ::API::Client::ActionResult.new(resource_hash) | ||
else | ||
klass.new(self, resource_hash) | ||
end | ||
end | ||
end | ||
|
||
def action_body(action_name, *args, &block) | ||
args = args.flatten | ||
args = args.first if args.size == 1 && args.first.kind_of?(Hash) | ||
args = {} if args.blank? | ||
block_data = block ? block.call : {} | ||
body = { "action" => action_name } | ||
if block_data.present? | ||
if block_data.kind_of?(Array) | ||
body["resources"] = block_data.collect { |resource| resource.merge(args) } | ||
elsif args.present? && args.kind_of?(Array) | ||
body["resources"] = args.collect { |resource| resource.merge(block_data) } | ||
else | ||
body["resource"] = args.dup.merge!(block_data) | ||
end | ||
elsif args.present? | ||
body[args.kind_of?(Array) ? "resources" : "resource"] = args | ||
end | ||
body | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
module ManageIQ | ||
module API | ||
class Client | ||
class Subresource | ||
include ActionMixin | ||
|
||
CUSTOM_INSPECT_EXCLUSIONS = [:@resource].freeze | ||
include CustomInspectMixin | ||
|
||
def self.subclass(name) | ||
name = name.classify | ||
|
||
if const_defined?(name, false) | ||
const_get(name, false) | ||
else | ||
const_set(name, Class.new(self)) | ||
end | ||
end | ||
|
||
attr_reader :attributes | ||
attr_reader :subcollection | ||
attr_reader :actions | ||
|
||
delegate :client, :to => :resource | ||
delegate :resource, :to => :subcollection | ||
|
||
def initialize(subcollection, resource_hash) | ||
raise "Cannot instantiate a Subresource directly" if instance_of?(Subresource) | ||
@subcollection = subcollection | ||
@attributes = resource_hash.except("actions") | ||
add_href | ||
fetch_actions(resource_hash) | ||
end | ||
|
||
def [](attr) | ||
attr_str = attr.to_s | ||
attributes[attr_str] if attributes.key?(attr_str) | ||
end | ||
|
||
private | ||
|
||
def method_missing(sym, *args, &block) | ||
reload_actions unless actions_present? | ||
if attributes.key?(sym.to_s) | ||
attributes[sym.to_s] | ||
elsif action_defined?(sym) | ||
exec_action(sym, *args, &block) | ||
else | ||
super | ||
end | ||
end | ||
|
||
def respond_to_missing?(sym, *_) | ||
attributes.key?(sym.to_s) || action_defined?(sym) || super | ||
end | ||
|
||
def exec_action(name, args = nil, &block) | ||
args ||= {} | ||
raise "Action #{name} parameters must be a hash" if !args.kind_of?(Hash) | ||
action = find_action(name) | ||
res = client.send(action.method, URI(action.href)) do | ||
body = { "action" => action.name } | ||
resource = args.dup | ||
resource.merge!(block.call) if block | ||
resource.present? ? body.merge("resource" => resource) : body | ||
end | ||
action_result(res) | ||
end | ||
|
||
# Let's add href's here if not yet defined by the server | ||
def add_href | ||
return if attributes.key?("href") | ||
return unless attributes.key?("id") | ||
attributes["href"] = "#{resource.href}/#{self.class.name}/#{attributes['id']}" | ||
end | ||
|
||
def reload_actions | ||
return unless attributes.key?("href") | ||
resource_hash = client.get(attributes["href"]) | ||
@attributes = resource_hash.except("actions") | ||
fetch_actions(resource_hash) | ||
end | ||
end | ||
end | ||
end | ||
end |