diff --git a/aviator.gemspec b/aviator.gemspec index 7926940..664dc6f 100644 --- a/aviator.gemspec +++ b/aviator.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency 'faraday', '~> 0.8.0' - spec.add_dependency 'activesupport', '>= 3.2.8', '<= 4.0.0' + spec.add_dependency 'activesupport', '>= 3.2.8' spec.add_dependency 'thor', '~> 0.18.1' spec.add_development_dependency "bundler", "~> 1.3" diff --git a/lib/aviator.rb b/lib/aviator.rb new file mode 100644 index 0000000..d9c6cc4 --- /dev/null +++ b/lib/aviator.rb @@ -0,0 +1 @@ +require 'aviator/core' diff --git a/lib/aviator/core.rb b/lib/aviator/core.rb index 7c4138b..c8522ae 100644 --- a/lib/aviator/core.rb +++ b/lib/aviator/core.rb @@ -3,6 +3,7 @@ require 'active_support/inflector' require 'faraday' require "aviator/version" +require "aviator/core/dsl" require "aviator/core/request" require "aviator/core/response" require "aviator/core/service" diff --git a/lib/aviator/core/dsl.rb b/lib/aviator/core/dsl.rb new file mode 100644 index 0000000..96e08db --- /dev/null +++ b/lib/aviator/core/dsl.rb @@ -0,0 +1,37 @@ +module Aviator + + class << self + + def define_request(request_name, &block) + class_obj = Class.new(Request, &block) + + build_or_get_request_class( + Aviator, + class_obj, + + class_obj.provider, + class_obj.service, + class_obj.api_version, + class_obj.endpoint_type, + request_name + ) + end + + + private + + def build_or_get_request_class(base, obj, *hierarchy) + const_name = hierarchy.shift.to_s.camelize + + const = if base.const_defined?(const_name) + base.const_get(const_name) + else + base.const_set(const_name, (hierarchy.empty? ? obj : Module.new)) + end + + hierarchy.empty? ? const : build_or_get_request_class(const, obj, *hierarchy) + end + + end # class << self + +end # module Aviator \ No newline at end of file diff --git a/lib/aviator/core/request.rb b/lib/aviator/core/request.rb index 3a9ebf7..bf5e8bc 100644 --- a/lib/aviator/core/request.rb +++ b/lib/aviator/core/request.rb @@ -191,6 +191,15 @@ module Aviator end + def provider(value=nil) + if value + @provider = value + else + @provider + end + end + + def optional_params @optional_params ||= [] end @@ -206,6 +215,15 @@ module Aviator end + def service(value=nil) + if value + @service = value + else + @service + end + end + + def url? instance_methods.include? :url end diff --git a/lib/aviator/core/service.rb b/lib/aviator/core/service.rb index f55e70f..15f934e 100644 --- a/lib/aviator/core/service.rb +++ b/lib/aviator/core/service.rb @@ -45,29 +45,6 @@ module Aviator end end - # Because we define requests in a flattened scope, we want to make sure that when each - # request is initialized it doesn't get polluted by instance variables and methods - # of the containing class. This builder class makes that happen by being a - # scope gate for the file. See Metaprogramming Ruby, specifically on blocks and scope - class RequestBuilder - - # This method gets called by the request file eval'd in self.build below - def define_request(request_name, &block) - klass = Class.new(Aviator::Request, &block) - return klass, request_name - end - - - def self.build(path_to_request_file) - clean_room = new - clean_room.instance_eval(File.read(path_to_request_file)) - end - - - private_class_method :new - - end - attr_accessor :default_session_data @@ -134,19 +111,27 @@ module Aviator # Candidate for extraction to aviator/openstack def find_request(name, session_data, endpoint_type=nil) endpoint_types = if endpoint_type - [endpoint_type.to_sym] + [endpoint_type.to_s.camelize] else - [:public, :admin] + ['Public', 'Admin'] end - version = infer_version(session_data) + namespace = Aviator.const_get(provider.camelize) + .const_get(service.camelize) - return nil unless version && requests[version] + version = infer_version(session_data).to_s.camelize + + return nil unless version && namespace.const_defined?(version) + + namespace = namespace.const_get(version) endpoint_types.each do |endpoint_type| - next unless requests[version][endpoint_type] - pair = requests[version][endpoint_type].find{ |k, v| k == name } - return pair[1] unless pair.nil? + name = name.to_s.camelize + + next unless namespace.const_defined?(endpoint_type) + next unless namespace.const_get(endpoint_type).const_defined?(name) + + return namespace.const_get(endpoint_type).const_get(name) end nil @@ -182,22 +167,14 @@ module Aviator ).expand_path ) - @requests ||= {} - - request_file_paths.each do |path_to_file| - klass, request_name = RequestBuilder.build(path_to_file) - - api_version = @requests[klass.api_version] ||= {} - endpoint_type = api_version[klass.endpoint_type] ||= {} - endpoint_type[request_name] = klass - end + request_file_paths.each{ |path| require path } end def log_file @log_file end - + end end \ No newline at end of file diff --git a/lib/aviator/openstack/compute/v2/public/list_images.rb b/lib/aviator/openstack/compute/v2/public/list_images.rb index f0042b5..a79497a 100644 --- a/lib/aviator/openstack/compute/v2/public/list_images.rb +++ b/lib/aviator/openstack/compute/v2/public/list_images.rb @@ -1,7 +1,10 @@ -define_request :list_images do +Aviator.define_request :list_images do - endpoint_type :public + provider :openstack + service :compute api_version :v2 + endpoint_type :public + http_method :get link_to 'documentation', diff --git a/lib/aviator/openstack/identity/v2/admin/create_tenant.rb b/lib/aviator/openstack/identity/v2/admin/create_tenant.rb index 37c591d..ab14e9b 100644 --- a/lib/aviator/openstack/identity/v2/admin/create_tenant.rb +++ b/lib/aviator/openstack/identity/v2/admin/create_tenant.rb @@ -1,7 +1,10 @@ -define_request :create_tenant do +Aviator.define_request :create_tenant do - endpoint_type :admin + provider :openstack + service :identity api_version :v2 + endpoint_type :admin + http_method :post link_to 'documentation', diff --git a/lib/aviator/openstack/identity/v2/admin/list_tenants.rb b/lib/aviator/openstack/identity/v2/admin/list_tenants.rb index cac9664..ecf7a6c 100644 --- a/lib/aviator/openstack/identity/v2/admin/list_tenants.rb +++ b/lib/aviator/openstack/identity/v2/admin/list_tenants.rb @@ -1,7 +1,10 @@ -define_request :list_tenants do +Aviator.define_request :list_tenants do - endpoint_type :admin + provider :openstack + service :identity api_version :v2 + endpoint_type :admin + http_method :get link_to 'documentation', diff --git a/lib/aviator/openstack/identity/v2/public/create_token.rb b/lib/aviator/openstack/identity/v2/public/create_token.rb index ccd029e..0dd78a2 100644 --- a/lib/aviator/openstack/identity/v2/public/create_token.rb +++ b/lib/aviator/openstack/identity/v2/public/create_token.rb @@ -1,9 +1,12 @@ -define_request :create_token do +Aviator.define_request :create_token do anonymous - endpoint_type :public + provider :openstack + service :identity api_version :v2 + endpoint_type :public + http_method :post link_to 'documentation', diff --git a/lib/aviator/openstack/identity/v2/public/list_tenants.rb b/lib/aviator/openstack/identity/v2/public/list_tenants.rb index b8a6bc4..a647d04 100644 --- a/lib/aviator/openstack/identity/v2/public/list_tenants.rb +++ b/lib/aviator/openstack/identity/v2/public/list_tenants.rb @@ -1,7 +1,10 @@ -define_request :list_tenants do +Aviator.define_request :list_tenants do - endpoint_type :public + provider :openstack + service :identity api_version :v2 + endpoint_type :public + http_method :get link_to 'documentation', diff --git a/test.rb b/test.rb new file mode 100644 index 0000000..d7325a0 --- /dev/null +++ b/test.rb @@ -0,0 +1,83 @@ +require 'pry' +require 'active_support/inflector' + +module Aviator + + class << self + + def define_request(request_name, &block) + class_obj = Class.new(Object, &block) + + build_or_get_request_class( + Aviator, + class_obj, + + class_obj.provider, + class_obj.service, + class_obj.api_version, + class_obj.endpoint_type, + request_name + ) + end + + + private + + def build_or_get_request_class(base, obj, *hierarchy) + const_name = hierarchy.shift.to_s.classify + + const = if base.const_defined?(const_name) + base.const_get(const_name) + else + base.const_set(const_name, (hierarchy.empty? ? obj : Module.new)) + end + + hierarchy.empty? ? const : build_or_get_request_class(const, obj, *hierarchy) + end + + end # class << self + +end # module Aviator + +test1 = Aviator.define_request :test1 do + + def self.provider + :openstack + end + + def self.service + :identity + end + + def self.api_version + :v2 + end + + def self.endpoint_type + :admin + end + +end + + +test2 = Aviator.define_request :test2 do + + def self.provider + :openstack + end + + def self.service + :identity + end + + def self.api_version + :v3 + end + + def self.endpoint_type + :admin + end + +end + +binding.pry \ No newline at end of file diff --git a/test/aviator/core/service_test.rb b/test/aviator/core/service_test.rb index 3c61138..280d512 100644 --- a/test/aviator/core/service_test.rb +++ b/test/aviator/core/service_test.rb @@ -22,21 +22,21 @@ class Aviator::Test end end end - - + + def klass Aviator::Service end - + def service(default_session_data=nil) options = { provider: config[:provider], service: config[:auth_service][:name] } - + options[:default_session_data] = default_session_data unless default_session_data.nil? - + klass.new(options) end @@ -45,7 +45,7 @@ class Aviator::Test it 'can find the correct request based on bootstrapped session data' do response = do_auth_request - + response.must_be_instance_of Aviator::Response response.request.api_version.must_equal config[:auth_service][:api_version].to_sym end @@ -67,26 +67,26 @@ class Aviator::Test params[k] = v end end - + response.must_be_instance_of Aviator::Response response.request.api_version.must_equal :v2 response.status.must_equal 200 end - - + + it 'can find the correct request based on non-bootstrapped session data' do session_data = do_auth_request.body - + response = service.request :create_tenant, session_data: session_data do |params| params.name = 'Test Project' params.description = 'This is a test' params.enabled = true end - + response.status.must_equal 200 end - - + + it 'uses the default session data if session data is not provided' do default_session_data = do_auth_request.body s = service(default_session_data) @@ -96,11 +96,11 @@ class Aviator::Test params.description = 'This is a test' params.enabled = true end - + response.status.must_equal 200 end - - + + it 'raises a SessionDataNotProvidedError if there is no session data' do the_method = lambda do service.request :create_tenant do |params| @@ -109,28 +109,28 @@ class Aviator::Test params.enabled = true end end - + the_method.must_raise Aviator::Service::SessionDataNotProvidedError error = the_method.call rescue $! error.message.wont_be_nil end - - + + it 'accepts an endpoint type option for selecting a specific request' do default_session_data = do_auth_request.body s = service(default_session_data) - + response1 = s.request :list_tenants, endpoint_type: 'admin' response2 = s.request :list_tenants, endpoint_type: 'public' - + response1.request.url.wont_equal response2.request.url end end - - + + describe '#default_session_data=' do - + it 'sets the service\'s default session data' do bootstrap = { auth_service: { @@ -139,18 +139,18 @@ class Aviator::Test request: 'create_token' } } - + svc = service(bootstrap) session_data_1 = svc.default_session_data session_data_2 = do_auth_request.body - + svc.default_session_data = session_data_2 - + svc.default_session_data.wont_equal session_data_1 svc.default_session_data.must_equal session_data_2 end - + end end diff --git a/test/aviator/openstack/compute/v2/public/list_images_test.rb b/test/aviator/openstack/compute/v2/public/list_images_test.rb index 4138e96..20b156d 100644 --- a/test/aviator/openstack/compute/v2/public/list_images_test.rb +++ b/test/aviator/openstack/compute/v2/public/list_images_test.rb @@ -32,9 +32,7 @@ class Aviator::Test def klass - path = helper.request_path('compute', 'v2', 'public', 'list_images.rb') - klass, request_name = Aviator::Service::RequestBuilder.build(path) - klass + @klass ||= helper.load_request('openstack', 'compute', 'v2', 'public', 'list_images.rb') end @@ -105,13 +103,13 @@ class Aviator::Test [ :status, 'ACTIVE' ], [ :type, 'application/vnd.openstack.image' ] ] - + url += "/detail" if params.first[1] filters = [] - + params[1, params.length-1].each { |pair| filters << "#{ pair[0] }=#{ pair[1] }" } - + url += "?#{ filters.join('&') }" unless filters.empty? request = klass.new(session_data) do |p| @@ -154,8 +152,8 @@ class Aviator::Test response.body[:images].length.must_equal 0 response.headers.wont_be_nil end - - + + validate_response 'parameters are valid' do service = Aviator::Service.new( provider: 'openstack', diff --git a/test/aviator/openstack/identity/v2/admin/create_tenant_test.rb b/test/aviator/openstack/identity/v2/admin/create_tenant_test.rb index 8dc60d1..7139e74 100644 --- a/test/aviator/openstack/identity/v2/admin/create_tenant_test.rb +++ b/test/aviator/openstack/identity/v2/admin/create_tenant_test.rb @@ -36,9 +36,7 @@ class Aviator::Test def klass - path = helper.request_path('identity', 'v2', 'admin', 'create_tenant.rb') - klass, request_name = Aviator::Service::RequestBuilder.build(path) - klass + @klass ||= helper.load_request('openstack', 'identity', 'v2', 'admin', 'create_tenant.rb') end diff --git a/test/aviator/openstack/identity/v2/public/create_token_test.rb b/test/aviator/openstack/identity/v2/public/create_token_test.rb index 62fe94d..37767e0 100644 --- a/test/aviator/openstack/identity/v2/public/create_token_test.rb +++ b/test/aviator/openstack/identity/v2/public/create_token_test.rb @@ -18,9 +18,7 @@ class Aviator::Test def klass - path = helper.request_path('identity', 'v2', 'public', 'create_token.rb') - klass, request_name = Aviator::Service::RequestBuilder.build(path) - klass + @klass ||= helper.load_request('openstack', 'identity', 'v2', 'public', 'create_token.rb') end diff --git a/test/support/openstack_request_test_helper.rb b/test/support/request_helper.rb similarity index 85% rename from test/support/openstack_request_test_helper.rb rename to test/support/request_helper.rb index 968e33f..28889c7 100644 --- a/test/support/openstack_request_test_helper.rb +++ b/test/support/request_helper.rb @@ -44,12 +44,31 @@ class Test auth_service: Environment.openstack_admin[:auth_service] } end + + + def get_request_class(parent, *path) + const_name = path.shift.to_s.camelize.gsub(/\.rb$/, '') + + const = if parent.const_defined?(const_name) + parent.const_get(const_name) + else + raise "Constant #{ const_name } could not be found." + end + + path.empty? ? const : get_request_class(const, *path) + end + + + def load_request(*path) + require request_path(*path) + get_request_class(Aviator, *path) + end def request_path(*path) - Pathname.new(__FILE__).join('..', '..', '..', 'lib', 'aviator', 'openstack').expand_path.join(*path) + Pathname.new(__FILE__).join('..', '..', '..', 'lib', 'aviator').expand_path.join(*path) end - + end end diff --git a/test/support/test_reporter.rb b/test/support/test_reporter.rb index b49a200..0e99ee0 100644 --- a/test/support/test_reporter.rb +++ b/test/support/test_reporter.rb @@ -1,6 +1,7 @@ require "minitest/reporters" -module MiniTest::Reporters::RedStack +module Aviator +class Test class SpecReporter < MiniTest::Reporters::SpecReporter @@ -41,5 +42,6 @@ module MiniTest::Reporters::RedStack end end +end -MiniTest::Reporters.use! MiniTest::Reporters::RedStack::ProgressReporter.new \ No newline at end of file +MiniTest::Reporters.use! Aviator::Test::ProgressReporter.new \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 2476939..8964cd3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -12,6 +12,9 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ ] SimpleCov.start do add_filter '/test/' + + add_group 'Core', 'lib/aviator/core' + add_group 'OpenStack', 'lib/aviator/openstack' end require 'minitest/autorun' @@ -32,4 +35,4 @@ Dir[Pathname.new(__FILE__).join('..', 'support', '*.rb')].each do |f| require f end -require 'aviator/core' +require 'aviator'