aviator 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. data/.coveralls.yml +1 -0
  2. data/.gitignore +6 -1
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +12 -0
  6. data/Gemfile +13 -0
  7. data/Guardfile +6 -0
  8. data/LICENSE.txt +1 -1
  9. data/README.md +151 -2
  10. data/Rakefile +8 -0
  11. data/aviator.gemspec +14 -1
  12. data/bin/aviator +110 -0
  13. data/lib/aviator/core.rb +9 -0
  14. data/lib/aviator/core/request.rb +229 -0
  15. data/lib/aviator/core/response.rb +33 -0
  16. data/lib/aviator/core/service.rb +203 -0
  17. data/lib/aviator/core/session.rb +187 -0
  18. data/lib/aviator/openstack/compute/v2/public/list_images.rb +49 -0
  19. data/lib/aviator/openstack/identity/v2/admin/create_tenant.rb +42 -0
  20. data/lib/aviator/openstack/identity/v2/admin/list_tenants.rb +43 -0
  21. data/lib/aviator/openstack/identity/v2/public/create_token.rb +61 -0
  22. data/lib/aviator/openstack/identity/v2/public/list_tenants.rb +44 -0
  23. data/lib/aviator/version.rb +1 -1
  24. data/test/aviator/core/request_test.rb +342 -0
  25. data/test/aviator/core/service_test.rb +158 -0
  26. data/test/aviator/core/session_test.rb +201 -0
  27. data/test/aviator/openstack/compute/v2/public/list_images_test.rb +179 -0
  28. data/test/aviator/openstack/identity/v2/admin/create_tenant_test.rb +151 -0
  29. data/test/aviator/openstack/identity/v2/public/create_token_test.rb +159 -0
  30. data/test/cassettes/core/service/i_default_session_data_/sets_the_service_s_default_session_data.yml +63 -0
  31. data/test/cassettes/core/service/i_request/accepts_an_endpoint_type_option_for_selecting_a_specific_request.yml +140 -0
  32. data/test/cassettes/core/service/i_request/can_find_the_correct_request_based_on_bootstrapped_session_data.yml +63 -0
  33. data/test/cassettes/core/service/i_request/can_find_the_correct_request_based_on_non-bootstrapped_session_data.yml +97 -0
  34. data/test/cassettes/core/service/i_request/can_find_the_correct_request_if_api_version_is_not_defined_but_can_be_inferred_from_host_uri.yml +63 -0
  35. data/test/cassettes/core/service/i_request/uses_the_default_session_data_if_session_data_is_not_provided.yml +97 -0
  36. data/test/cassettes/core/session/c_load/creates_a_new_instance_from_the_given_session_dump.yml +63 -0
  37. data/test/cassettes/core/session/c_load/uses_the_loaded_auth_info_for_its_services.yml +63 -0
  38. data/test/cassettes/core/session/c_new/directs_log_entries_to_the_given_log_file.yml +63 -0
  39. data/test/cassettes/core/session/i_authenticate/authenticates_against_the_auth_service_indicated_in_the_config_file.yml +63 -0
  40. data/test/cassettes/core/session/i_authenticate/authenticates_against_the_auth_service_using_the_credentials_in_the_given_block.yml +38 -0
  41. data/test/cassettes/core/session/i_authenticate/raises_an_AuthenticationError_when_authentication_fails.yml +35 -0
  42. data/test/cassettes/core/session/i_authenticate/updates_the_session_data_of_its_service_objects.yml +123 -0
  43. data/test/cassettes/core/session/i_dump/serializes_the_session_data_for_caching.yml +63 -0
  44. data/test/cassettes/core/session/i_validate/returns_false_if_session_is_no_longer_valid.yml +97 -0
  45. data/test/cassettes/core/session/i_validate/returns_true_if_session_is_still_valid.yml +98 -0
  46. data/test/cassettes/core/session/i_xxx_service/returns_an_instance_of_the_indicated_service.yml +63 -0
  47. data/test/cassettes/openstack/compute/v2/public/list_images/leads_to_a_valid_response_when_no_parameters_are_provided.yml +108 -0
  48. data/test/cassettes/openstack/compute/v2/public/list_images/leads_to_a_valid_response_when_parameters_are_invalid.yml +96 -0
  49. data/test/cassettes/openstack/compute/v2/public/list_images/leads_to_a_valid_response_when_parameters_are_valid.yml +103 -0
  50. data/test/cassettes/openstack/compute/v2/public/list_images/returns_the_correct_value_for_body.yml +63 -0
  51. data/test/cassettes/openstack/compute/v2/public/list_images/returns_the_correct_value_for_body_.yml +63 -0
  52. data/test/cassettes/openstack/compute/v2/public/list_images/returns_the_correct_value_for_headers.yml +63 -0
  53. data/test/cassettes/openstack/compute/v2/public/list_images/returns_the_correct_value_for_headers_.yml +63 -0
  54. data/test/cassettes/openstack/compute/v2/public/list_images/returns_the_correct_value_for_url.yml +63 -0
  55. data/test/cassettes/openstack/compute/v2/public/list_images/returns_the_correct_value_for_url_.yml +63 -0
  56. data/test/cassettes/openstack/identity/v2/admin/create_tenant/leads_to_a_valid_response_when_params_are_invalid.yml +97 -0
  57. data/test/cassettes/openstack/identity/v2/admin/create_tenant/leads_to_a_valid_response_when_params_are_valid.yml +97 -0
  58. data/test/cassettes/openstack/identity/v2/admin/create_tenant/leads_to_a_valid_response_when_provided_with_invalid_params.yml +97 -0
  59. data/test/cassettes/openstack/identity/v2/public/create_token/leads_to_a_valid_response_when_parameters_are_invalid.yml +35 -0
  60. data/test/cassettes/openstack/identity/v2/public/create_token/leads_to_a_valid_response_when_parameters_are_valid.yml +38 -0
  61. data/test/cassettes/openstack/identity/v2/public/create_token/leads_to_a_valid_response_when_provided_with_a_token.yml +98 -0
  62. data/test/environment.yml.example +22 -0
  63. data/test/environment.yml.travis-ci +24 -0
  64. data/test/support/openstack_request_test_helper.rb +57 -0
  65. data/test/support/test_base_class.rb +39 -0
  66. data/test/support/test_environment.rb +37 -0
  67. data/test/support/test_reporter.rb +45 -0
  68. data/test/support/vcr_setup.rb +41 -0
  69. data/test/test_helper.rb +35 -0
  70. data/tmp/.gitignore +0 -0
  71. metadata +310 -8
  72. data/lib/aviator.rb +0 -5
@@ -0,0 +1,33 @@
1
+ module Aviator
2
+
3
+ class Response
4
+
5
+
6
+ def method_missing(name, *args)
7
+ case name
8
+ when :body
9
+ @body ||= JSON.parse(response.body).with_indifferent_access
10
+ @body.dup
11
+ when :headers, :status
12
+ response.send(name)
13
+ when :request
14
+ request
15
+ else
16
+ super(name, *args)
17
+ end
18
+ end
19
+
20
+
21
+ private
22
+
23
+ attr_reader :response,
24
+ :request
25
+
26
+ def initialize(response, request)
27
+ @response = response
28
+ @request = request
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,203 @@
1
+ module Aviator
2
+
3
+ class Service
4
+
5
+ class AccessDetailsNotDefinedError < StandardError
6
+ def initialize
7
+ super ":access_details is not defined."
8
+ end
9
+ end
10
+
11
+ class ProviderNotDefinedError < StandardError
12
+ def initialize
13
+ super ":provider is not defined."
14
+ end
15
+ end
16
+
17
+ class ServiceNameNotDefinedError < StandardError
18
+ def initialize
19
+ super ":service is not defined."
20
+ end
21
+ end
22
+
23
+ class SessionDataNotProvidedError < StandardError
24
+ def initialize
25
+ super "default_session_data is not initialized and no session data was "\
26
+ "provided in the method call."
27
+ end
28
+ end
29
+
30
+
31
+ class UnknownRequestError < StandardError
32
+ def initialize(request_name)
33
+ super "Unknown request #{ request_name }."
34
+ end
35
+ end
36
+
37
+
38
+ class Logger < Faraday::Response::Logger
39
+ def initialize(app, logger=nil)
40
+ super(app)
41
+ @logger = logger || begin
42
+ require 'logger'
43
+ ::Logger.new(self.class::LOG_FILE_PATH)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Because we define requests in a flattened scope, we want to make sure that when each
49
+ # request is initialized it doesn't get polluted by instance variables and methods
50
+ # of the containing class. This builder class makes that happen by being a
51
+ # scope gate for the file. See Metaprogramming Ruby, specifically on blocks and scope
52
+ class RequestBuilder
53
+
54
+ # This method gets called by the request file eval'd in self.build below
55
+ def define_request(request_name, &block)
56
+ klass = Class.new(Aviator::Request, &block)
57
+ return klass, request_name
58
+ end
59
+
60
+
61
+ def self.build(path_to_request_file)
62
+ clean_room = new
63
+ clean_room.instance_eval(File.read(path_to_request_file))
64
+ end
65
+
66
+
67
+ private_class_method :new
68
+
69
+ end
70
+
71
+
72
+ attr_accessor :default_session_data
73
+
74
+ attr_reader :service,
75
+ :provider
76
+
77
+
78
+ def initialize(opts={})
79
+ @provider = opts[:provider] || (raise ProviderNotDefinedError.new)
80
+ @service = opts[:service] || (raise ServiceNameNotDefinedError.new)
81
+ @log_file = opts[:log_file]
82
+
83
+ @default_session_data = opts[:default_session_data]
84
+
85
+ load_requests
86
+ end
87
+
88
+
89
+ def request(request_name, options={}, &params)
90
+ session_data = options[:session_data] || default_session_data
91
+
92
+ raise SessionDataNotProvidedError.new unless session_data
93
+
94
+ request_class = find_request(request_name, session_data, options[:endpoint_type])
95
+
96
+ raise UnknownRequestError.new(request_name) unless request_class
97
+
98
+ request = request_class.new(session_data, &params)
99
+
100
+ response = http_connection.send(request.http_method) do |r|
101
+ r.url request.url
102
+ r.headers.merge!(request.headers) if request.headers?
103
+ r.query = request.querystring if request.querystring?
104
+ r.body = JSON.generate(request.body) if request.body?
105
+ end
106
+
107
+ Aviator::Response.send(:new, response, request)
108
+ end
109
+
110
+
111
+ def requests
112
+ @requests
113
+ end
114
+
115
+
116
+ private
117
+
118
+
119
+ def http_connection
120
+ @http_connection ||= Faraday.new do |conn|
121
+ if log_file
122
+ # Ugly hack to make logger configurable
123
+ const_name = 'LOG_FILE_PATH'
124
+ Logger.send(:remove_const, const_name) if Logger.const_defined?(const_name)
125
+ Logger.const_set(const_name, log_file)
126
+ conn.use Logger.dup
127
+ end
128
+ conn.adapter Faraday.default_adapter
129
+ conn.headers['Content-Type'] = 'application/json'
130
+ end
131
+ end
132
+
133
+
134
+ # Candidate for extraction to aviator/openstack
135
+ def find_request(name, session_data, endpoint_type=nil)
136
+ endpoint_types = if endpoint_type
137
+ [endpoint_type.to_sym]
138
+ else
139
+ [:public, :admin]
140
+ end
141
+
142
+ version = infer_version(session_data)
143
+
144
+ return nil unless version && requests[version]
145
+
146
+ endpoint_types.each do |endpoint_type|
147
+ next unless requests[version][endpoint_type]
148
+ pair = requests[version][endpoint_type].find{ |k, v| k == name }
149
+ return pair[1] unless pair.nil?
150
+ end
151
+
152
+ nil
153
+ end
154
+
155
+
156
+ # Candidate for extraction to aviator/openstack
157
+ def infer_version(session_data)
158
+ if session_data.has_key?(:auth_service) && session_data[:auth_service][:api_version]
159
+ session_data[:auth_service][:api_version].to_sym
160
+
161
+ elsif session_data.has_key?(:auth_service) && session_data[:auth_service][:host_uri]
162
+ m = session_data[:auth_service][:host_uri].match(/(v\d+)\.?\d*/)
163
+ return m[1].to_sym unless m.nil?
164
+
165
+ elsif session_data.has_key? :access
166
+ service_spec = session_data[:access][:serviceCatalog].find{|s| s[:type] == service }
167
+ service_spec[:endpoints][0][:publicURL].match(/(v\d+)\.?\d*/)[1].to_sym
168
+ end
169
+ end
170
+
171
+
172
+ def load_requests
173
+ # TODO: This should be determined by a provider-specific module.
174
+ # e.g. Aviator::OpenStack::requests_base_dir
175
+ request_file_paths = Dir.glob(Pathname.new(__FILE__).join(
176
+ '..',
177
+ '..',
178
+ provider.to_s,
179
+ service.to_s,
180
+ '**',
181
+ '*.rb'
182
+ ).expand_path
183
+ )
184
+
185
+ @requests ||= {}
186
+
187
+ request_file_paths.each do |path_to_file|
188
+ klass, request_name = RequestBuilder.build(path_to_file)
189
+
190
+ api_version = @requests[klass.api_version] ||= {}
191
+ endpoint_type = api_version[klass.endpoint_type] ||= {}
192
+ endpoint_type[request_name] = klass
193
+ end
194
+ end
195
+
196
+
197
+ def log_file
198
+ @log_file
199
+ end
200
+
201
+ end
202
+
203
+ end
@@ -0,0 +1,187 @@
1
+ module Aviator
2
+
3
+ class Session
4
+
5
+ class AuthenticationError < StandardError
6
+ def initialize(last_auth_body)
7
+ super("Authentication failed. The server returned #{ last_auth_body }")
8
+ end
9
+ end
10
+
11
+
12
+ class EnvironmentNotDefinedError < ArgumentError
13
+ def initialize(path, env)
14
+ super("The environment '#{ env }' is not defined in #{ path }.")
15
+ end
16
+ end
17
+
18
+
19
+ class InvalidConfigFilePathError < ArgumentError
20
+ def initialize(path)
21
+ super("The config file at #{ path } does not exist!")
22
+ end
23
+ end
24
+
25
+
26
+ class NotAuthenticatedError < StandardError
27
+ def initialize
28
+ super("Session is not authenticated. Please authenticate before proceeding.")
29
+ end
30
+ end
31
+
32
+
33
+ class ValidatorNotDefinedError < StandardError
34
+ def initialize
35
+ super("The validator request name is not defined for this session object.")
36
+ end
37
+ end
38
+
39
+
40
+ def initialize(opts={})
41
+ config_path = opts[:config_file]
42
+ environment = opts[:environment]
43
+ session_dump = opts[:session_dump]
44
+
45
+ if session_dump
46
+ initialize_with_dump(session_dump)
47
+ else
48
+ initialize_with_config(config_path, environment)
49
+ end
50
+
51
+ @log_file = opts[:log_file]
52
+ end
53
+
54
+
55
+ def authenticate(&block)
56
+ block ||= lambda do |params|
57
+ environment[:auth_credentials].each do |key, value|
58
+ params[key] = value
59
+ end
60
+ end
61
+
62
+ response = auth_service.request environment[:auth_service][:request].to_sym, &block
63
+
64
+ if response.status == 200
65
+ @auth_info = response.body
66
+ update_services_session_data
67
+ else
68
+ raise AuthenticationError.new(response.body)
69
+ end
70
+ end
71
+
72
+
73
+ def authenticated?
74
+ !auth_info.nil?
75
+ end
76
+
77
+
78
+ def dump
79
+ JSON.generate({
80
+ environment: environment,
81
+ auth_info: auth_info
82
+ })
83
+ end
84
+
85
+
86
+ def method_missing(name, *args, &block)
87
+ service_name_parts = name.to_s.match(/^(\w+)_service$/)
88
+
89
+ if service_name_parts
90
+ get_service_obj(service_name_parts[1])
91
+ else
92
+ super name, *args, &block
93
+ end
94
+ end
95
+
96
+
97
+ def self.load(session_dump, opts={})
98
+ opts[:session_dump] = session_dump
99
+
100
+ new(opts)
101
+ end
102
+
103
+
104
+ def validate
105
+ raise NotAuthenticatedError.new unless authenticated?
106
+ raise ValidatorNotDefinedError.new unless environment[:auth_service][:validator]
107
+
108
+ response = auth_service.request environment[:auth_service][:validator].to_sym, session_data: auth_info
109
+
110
+ response.status == 200 || response.status == 203
111
+ end
112
+
113
+
114
+ private
115
+
116
+
117
+ def auth_info
118
+ @auth_info
119
+ end
120
+
121
+
122
+ def auth_service
123
+ @auth_service ||= Service.new(
124
+ provider: environment[:provider],
125
+ service: environment[:auth_service][:name],
126
+ default_session_data: { auth_service: environment[:auth_service] },
127
+ log_file: log_file
128
+ )
129
+ end
130
+
131
+
132
+ def environment
133
+ @environment
134
+ end
135
+
136
+
137
+ def get_service_obj(service_name)
138
+ raise NotAuthenticatedError.new unless self.authenticated?
139
+
140
+ @services ||= {}
141
+
142
+ @services[service_name] ||= Service.new(
143
+ provider: environment[:provider],
144
+ service: service_name,
145
+ default_session_data: auth_info,
146
+ log_file: log_file
147
+ )
148
+
149
+ @services[service_name]
150
+ end
151
+
152
+
153
+ def initialize_with_config(config_path, environment)
154
+ raise InvalidConfigFilePathError.new(config_path) unless Pathname.new(config_path).file?
155
+
156
+ config = YAML.load_file(config_path).with_indifferent_access
157
+
158
+
159
+ raise EnvironmentNotDefinedError.new(config_path, environment) unless config[environment]
160
+
161
+ @environment = config[environment]
162
+ end
163
+
164
+
165
+ def initialize_with_dump(session_dump)
166
+ session_info = JSON.parse(session_dump).with_indifferent_access
167
+ @environment = session_info[:environment]
168
+ @auth_info = session_info[:auth_info]
169
+ end
170
+
171
+
172
+ def log_file
173
+ @log_file
174
+ end
175
+
176
+
177
+ def update_services_session_data
178
+ return unless @services
179
+
180
+ @services.each do |name, obj|
181
+ obj.default_session_data = auth_info
182
+ end
183
+ end
184
+
185
+ end
186
+
187
+ end
@@ -0,0 +1,49 @@
1
+ define_request :list_images do
2
+
3
+ endpoint_type :public
4
+ api_version :v2
5
+ http_method :get
6
+
7
+ link_to 'documentation',
8
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Images-d1e4435.html'
9
+
10
+ optional_param :details
11
+ optional_param :server
12
+ optional_param :name
13
+ optional_param :status
14
+ optional_param 'changes-since'
15
+ optional_param :marker
16
+ optional_param :limit
17
+ optional_param :type
18
+
19
+
20
+
21
+ def url
22
+ service_spec = session_data[:access][:serviceCatalog].find{|s| s[:type] == 'compute' }
23
+
24
+ str = "#{ service_spec[:endpoints][0][:publicURL] }/images"
25
+ str += "/detail" if params[:details]
26
+
27
+ filters = []
28
+
29
+ (optional_params + required_params - [:details]).each do |param_name|
30
+ filters << "#{ param_name }=#{ params[param_name] }" if params[param_name]
31
+ end
32
+
33
+ str += "?#{ filters.join('&') }" unless filters.empty?
34
+
35
+ str
36
+ end
37
+
38
+
39
+ def headers
40
+ h = {}
41
+
42
+ unless self.anonymous?
43
+ h['X-Auth-Token'] = session_data[:access][:token][:id]
44
+ end
45
+
46
+ h
47
+ end
48
+
49
+ end