right_cloud_api_base 0.1.0

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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY +2 -0
  3. data/LICENSE +19 -0
  4. data/README.md +14 -0
  5. data/Rakefile +37 -0
  6. data/lib/base/api_manager.rb +707 -0
  7. data/lib/base/helpers/cloud_api_logger.rb +214 -0
  8. data/lib/base/helpers/http_headers.rb +239 -0
  9. data/lib/base/helpers/http_parent.rb +103 -0
  10. data/lib/base/helpers/http_request.rb +173 -0
  11. data/lib/base/helpers/http_response.rb +122 -0
  12. data/lib/base/helpers/net_http_patch.rb +31 -0
  13. data/lib/base/helpers/query_api_patterns.rb +862 -0
  14. data/lib/base/helpers/support.rb +270 -0
  15. data/lib/base/helpers/support.xml.rb +306 -0
  16. data/lib/base/helpers/utils.rb +380 -0
  17. data/lib/base/manager.rb +122 -0
  18. data/lib/base/parsers/json.rb +38 -0
  19. data/lib/base/parsers/plain.rb +36 -0
  20. data/lib/base/parsers/rexml.rb +83 -0
  21. data/lib/base/parsers/sax.rb +200 -0
  22. data/lib/base/routines/cache_validator.rb +184 -0
  23. data/lib/base/routines/connection_proxies/net_http_persistent_proxy.rb +194 -0
  24. data/lib/base/routines/connection_proxies/right_http_connection_proxy.rb +224 -0
  25. data/lib/base/routines/connection_proxy.rb +66 -0
  26. data/lib/base/routines/request_analyzer.rb +122 -0
  27. data/lib/base/routines/request_generator.rb +48 -0
  28. data/lib/base/routines/request_initializer.rb +52 -0
  29. data/lib/base/routines/response_analyzer.rb +152 -0
  30. data/lib/base/routines/response_parser.rb +79 -0
  31. data/lib/base/routines/result_wrapper.rb +75 -0
  32. data/lib/base/routines/retry_manager.rb +106 -0
  33. data/lib/base/routines/routine.rb +98 -0
  34. data/lib/right_cloud_api_base.rb +72 -0
  35. data/lib/right_cloud_api_base_version.rb +37 -0
  36. data/right_cloud_api_base.gemspec +63 -0
  37. data/spec/helpers/query_api_pattern_spec.rb +312 -0
  38. data/spec/helpers/support_spec.rb +211 -0
  39. data/spec/helpers/support_xml_spec.rb +207 -0
  40. data/spec/helpers/utils_spec.rb +179 -0
  41. data/spec/routines/connection_proxies/test_net_http_persistent_proxy_spec.rb +143 -0
  42. data/spec/routines/test_cache_validator_spec.rb +152 -0
  43. data/spec/routines/test_connection_proxy_spec.rb +44 -0
  44. data/spec/routines/test_request_analyzer_spec.rb +106 -0
  45. data/spec/routines/test_response_analyzer_spec.rb +132 -0
  46. data/spec/routines/test_response_parser_spec.rb +228 -0
  47. data/spec/routines/test_result_wrapper_spec.rb +63 -0
  48. data/spec/routines/test_retry_manager_spec.rb +84 -0
  49. data/spec/spec_helper.rb +15 -0
  50. metadata +215 -0
@@ -0,0 +1,214 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module RightScale
25
+ module CloudApi
26
+
27
+ # Cloud API Logger wraps the given logger or creates a new one which logs to null by default
28
+ # By default the logger logs at INFO level.
29
+ #
30
+ # Every log message should be associated with a log_filter(key). If no log filter(key) is
31
+ # specified, then a message is logged and is not filterable.
32
+ #
33
+ # Any log_message with its key specified in log_filters is logged. If log_filters are not
34
+ # specified then the log_messages which is not associated by the key is logged.
35
+ #
36
+ # If parent logger is set at DEBUG level then all the messages are logged by default.
37
+ #
38
+ class CloudApiLogger
39
+
40
+ # Attribute readers
41
+ #
42
+ # @return [Object]
43
+ # @example
44
+ # # no example
45
+ attr_reader :logger, :log_filters, :log_filter_patterns
46
+
47
+ # Initializes a new logger
48
+ #
49
+ # @param [Hash] options A set of options
50
+ # @option options [String,IO] :logger Creates a new Logger instance from teh given value.
51
+ # @option options [NilClass] :logger Creates a new Logger instance that logs to STDOUT.
52
+ # @option options [Array] :log_filters An array of topics to log (see {help}
53
+ # for list of keys)
54
+ # @option options [Array] :log_filter_patterns An array of Strings or RegExps of things
55
+ # that have to be filtered out from logs.
56
+ #
57
+ # @example
58
+ # new(:logger => STDOUT)
59
+ #
60
+ def initialize(options = {} , default_filters = [])
61
+ @logger = options[:logger]
62
+ if @logger.is_a?(String) || @logger.is_a?(IO)
63
+ @logger = ::Logger::new(options[:logger])
64
+ @logger.level = ::Logger::INFO
65
+ @logger.datetime_format = "%Y%m%d %H%M%S"
66
+ @logger.formatter = proc { |severity, datetime, progname, msg| "#{severity[/^./]} #{datetime.strftime("%y%m%d %H%M%S")}: #{msg}\n" }
67
+ elsif @logger.nil?
68
+ @logger = ::Logger::new(options.has_key?(:logger) ? '/dev/null' : STDOUT)
69
+ @logger.level = ::Logger::INFO
70
+ end
71
+ @log_filters = options[:log_filters]._blank? ? Array(default_filters) : Array(options[:log_filters])
72
+ @log_filter_patterns = options[:log_filter_patterns]._blank? ? [] : Array(options[:log_filter_patterns])
73
+ end
74
+
75
+
76
+ # Logs the message at INFO level
77
+ #
78
+ # @param [String] message The given message
79
+ # @param [Symbol] key The filtering key.
80
+ #
81
+ # @return [void]
82
+ # @example
83
+ # logger.info('some text')
84
+ #
85
+ def info(message, key = nil)
86
+ log(message, key, :info)
87
+ end
88
+
89
+
90
+ # Logs the message at WARN level
91
+ #
92
+ # @param [String] message The given message
93
+ # @param [Symbol] key The filtering key.
94
+ #
95
+ # @return [void]
96
+ # @example
97
+ # logger.warn('some text')
98
+ #
99
+ def warn(message, key = nil)
100
+ log(message, key, :warn)
101
+ end
102
+
103
+
104
+ # Logs the message at ERROR level
105
+ #
106
+ # @param [String] message The given message
107
+ # @param [Symbol] key The filtering key.
108
+ #
109
+ # @return [void]
110
+ # @example
111
+ # logger.error('some text')
112
+ #
113
+ def error(message, key = nil)
114
+ log(message, key, :error)
115
+ end
116
+
117
+
118
+ # Returns a helper hash with all the supported topics
119
+ #
120
+ # @return [Hash] A set of log filter keys with explanations.
121
+ #
122
+ # @example
123
+ # # no example
124
+ #
125
+ def self.help
126
+ {
127
+ :api_manager => "Enables ApiManager's logs",
128
+ :wrapper => "Enables Wrapper's logs",
129
+ :routine => "Enables Routine's logs",
130
+ :cache_validator => "Enables CacheValidator logs",
131
+ :request_analyzer => "Enables RequestAnalyzer logs",
132
+ :request_generator => "Enables RequestGenerator logs" ,
133
+ :request_generator_body => "Enables RequestGenerator body logging",
134
+ :response_analyzer => "Enables ResponseAnalizer logs",
135
+ :response_analyzer_body => "Enables ResponseAnalyzer body logging",
136
+ :response_analyzer_body_error => "Enables ResponseAnalyzer body logging on error",
137
+ :timer => "Enables timer logs",
138
+ :retry_manager => "Enables RetryManager logs",
139
+ :connection_proxy => "Enables ConnectionProxy logs",
140
+ :all => "Enables all the possible log topics"
141
+ }
142
+ end
143
+
144
+
145
+ # Logs the given message
146
+ #
147
+ # @param [String] message The message to be logged.
148
+ # @param [Symbol] key Filtering key.
149
+ # @param [Symbol] method Logging method (:debug, :info, :etc).
150
+ #
151
+ # @return [void]
152
+ # @example
153
+ # # no example
154
+ #
155
+ def log(message, key = nil, method = :debug)
156
+ if !key || log_filters.include?(key) || log_filters.include?(:all)
157
+ logger.__send__(method, "#{request_log_tag}#{filter_message(message)}")
158
+ end
159
+ end
160
+
161
+
162
+ # Generates a unique prefix
163
+ #
164
+ # It adds the prefix to every logged line so that it is easy to identify what log
165
+ # message is for what request.
166
+ #
167
+ # @return [String]
168
+ # @example
169
+ # # no example
170
+ #
171
+ def set_unique_prefix
172
+ @unique_prefix = RightScale::CloudApi::Utils::random(6)
173
+ end
174
+
175
+
176
+ # Resets current prefix
177
+ #
178
+ # @return [void]
179
+ # @example
180
+ # # no example
181
+ #
182
+ def reset_unique_prefix
183
+ @unique_prefix = nil
184
+ end
185
+
186
+
187
+ # Returns the logging tag
188
+ #
189
+ # @return [String]
190
+ # @example
191
+ # # no example
192
+ #
193
+ def request_log_tag
194
+ @unique_prefix._blank? ? "" : "[#{@unique_prefix}] "
195
+ end
196
+
197
+
198
+ # Filters out all 'secrets' that match to log_filter_patterns
199
+ #
200
+ # @param [String] message The given message.
201
+ #
202
+ # @return [String] The original message that has sencitive things filtered out.
203
+ #
204
+ # @example
205
+ # filter_message('my secret password') #=> 'my [FILTERED] password'
206
+ #
207
+ def filter_message(message)
208
+ message = message.dup
209
+ log_filter_patterns.each {|pattern| message.gsub!(pattern, '\1[FILTERED]\3')}
210
+ message
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,239 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module RightScale
25
+ module CloudApi
26
+
27
+ # HTTP Headers container.
28
+ #
29
+ # @api public
30
+ #
31
+ # The class makes it so that the headers always point to an array or values. It is a wrapper
32
+ # around Hash class where all the keys are always arrays.
33
+ #
34
+ class HTTPHeaders < BlankSlate
35
+
36
+ # Initializer
37
+ #
38
+ # @param [Hash] headers A set of HTTP headers where keys are the headers names and the values
39
+ # are the headers values.
40
+ #
41
+ # @example
42
+ # # no example
43
+ #
44
+ def initialize(headers={})
45
+ @headers = normalize(headers)
46
+ end
47
+
48
+
49
+ # Retrieves the given headers values by the header name
50
+ #
51
+ # @param [String] header The header name.
52
+ # @return [Array] The arrays of value(s).
53
+ #
54
+ # @example
55
+ # request[:agent] #=> ['something']
56
+ #
57
+ def [](header)
58
+ @headers[normalize_header(header)] || []
59
+ end
60
+
61
+
62
+ # Sets a header
63
+ #
64
+ # @param [String] header The header name.
65
+ # @param [String,Array] value The new value(s).
66
+ # @return [void]
67
+ #
68
+ # @example
69
+ # # Add a header
70
+ # request[:agent] = 'something'
71
+ # # Remove a header
72
+ # request[:agent] = nil
73
+ #
74
+ def []=(header, value)
75
+ value = normalize_value(value)
76
+ if value.nil?
77
+ delete(header)
78
+ else
79
+ @headers[normalize_header(header)] = value
80
+ end
81
+ end
82
+
83
+
84
+ # Deletes the given header
85
+ #
86
+ # @param [String] header The header name.
87
+ # @return [void]
88
+ #
89
+ # @example
90
+ # # Delete the header
91
+ # request.delete(:agent)
92
+ #
93
+ def delete(header)
94
+ @headers.delete(normalize_header(header))
95
+ end
96
+
97
+
98
+ # Merges the new headers into the existent list
99
+ #
100
+ # @param [Hash] headers The new headers.
101
+ #
102
+ # @return [Hash] A new hash containing the result.
103
+ #
104
+ # @example
105
+ # # Delete the header
106
+ # request.merge(:agent => 'foobar')
107
+ #
108
+ def merge(headers)
109
+ @headers.merge(normalize(headers))
110
+ end
111
+
112
+
113
+ # Merges the new headers into the current hash
114
+ #
115
+ # @param [Hash] headers The new headers.
116
+ #
117
+ # @return [Hash] Updates the current object and returns the resulting hash.
118
+ #
119
+ # @example
120
+ # # Delete the header
121
+ # request.merge!(:agent => 'foobar')
122
+ #
123
+ def merge!(headers)
124
+ @headers.merge!(normalize(headers))
125
+ end
126
+
127
+
128
+ # Set the given header unless it is set
129
+ #
130
+ # If the curent headers list already has any value for the given header the method does nothing.
131
+ #
132
+ # @param [String] header The header name.
133
+ # @param [String,Array] value The values to initialize the header with.
134
+ # @return [void]
135
+ #
136
+ # @example
137
+ # request.set_if_blank(:agent, 'something')
138
+ #
139
+ def set_if_blank(header, value)
140
+ self[header] = value if self[header].first._blank?
141
+ end
142
+
143
+
144
+ # Returns a new Hash instance with all the current headers
145
+ #
146
+ # @return [Hash] A new Hash with the headers.
147
+ #
148
+ # @example
149
+ # request.to_hash #=> A hash with headers
150
+ #
151
+ def to_hash
152
+ @headers.dup
153
+ end
154
+
155
+
156
+ # Displays the headers in a nice way
157
+ #
158
+ # @return [String] return_description
159
+ #
160
+ # @example
161
+ # ec2.response.headers.to_s #=>
162
+ # 'content-type: "text/xml;charset=UTF-8", server: "AmazonEC2", something: ["a", "b"]'
163
+ #
164
+ def to_s
165
+ @headers.to_a.map { |header, value| "#{header}: #{(value.size == 1 ? value.first : value).inspect}" } * ', '
166
+ end
167
+
168
+
169
+ # Feeds all the unknown methods to the underlaying hash object
170
+ #
171
+ # @return [Object]
172
+ # @example
173
+ # # no example
174
+ #
175
+ def method_missing(method_name, *args, &block)
176
+ @headers.__send__(method_name, *args, &block)
177
+ end
178
+
179
+
180
+ # Makes it so that a header is always a down cased String instance
181
+ #
182
+ # @api private
183
+ #
184
+ # @param [String,Symbol] header The header name.
185
+ # @return [String] The normalized header name.
186
+ # @example
187
+ # normalize_header(:Name) #=> 'name'
188
+ #
189
+ def normalize_header(header)
190
+ header.to_s.downcase
191
+ end
192
+ private :normalize_header
193
+
194
+
195
+ # Wraps the given value(s) into Array
196
+ #
197
+ # @api private
198
+ #
199
+ # @param [Array] value The original values.
200
+ # @return [String] The arrayified values or nil.
201
+ #
202
+ # @example
203
+ # normalize_header(nil) #=> nil
204
+ # normalize_header('a') #=> ['a']
205
+ # normalize_header(['a', 'b']) #=> ['a', 'b']
206
+ #
207
+ def normalize_value(value)
208
+ value.nil? ? nil : Utils::arrayify(value)
209
+ end
210
+ private :normalize_value
211
+
212
+
213
+ # Normalizes the given headers
214
+ #
215
+ # @api private
216
+ #
217
+ # The method makes it so that all the keys are downcased String instances and all
218
+ # the values are Arrays.
219
+ #
220
+ # @param [Hash] headers A hash of headers.
221
+ #
222
+ # @return [Hash] Returns a new hash with normilized keys and values.
223
+ #
224
+ # @example
225
+ # normalize(:Name => 'a') #=> {'name' => ['a']}
226
+ #
227
+ def normalize(headers)
228
+ result = {}
229
+ headers.each do |header, value|
230
+ header, value = normalize_header(header), normalize_value(value)
231
+ result[header] = value unless value.nil?
232
+ end
233
+ result
234
+ end
235
+ private :normalize
236
+ end
237
+
238
+ end
239
+ end
@@ -0,0 +1,103 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module RightScale
25
+ module CloudApi
26
+
27
+ # The parent class for HTTPRequest and HTTPResponse.
28
+ #
29
+ # @api public
30
+ #
31
+ # The class defines generic methods that are used by both Request and Response classes.
32
+ #
33
+ class HTTPParent
34
+
35
+ # Returns Net::HTTPResponse object
36
+ #
37
+ # @return [Net::HTTPResponse]
38
+ # @example
39
+ # # no example
40
+ #
41
+ attr_accessor :raw
42
+
43
+
44
+ # Returns the response body
45
+ #
46
+ # @return [String,nil]
47
+ # @example
48
+ # # no example
49
+ #
50
+ attr_accessor :body
51
+
52
+
53
+ # Returns all the headers for the current request/response instance
54
+ #
55
+ # @return [Hash] The set of headers.
56
+ # @example
57
+ # # no example
58
+ #
59
+ def headers
60
+ @headers.to_hash
61
+ end
62
+
63
+
64
+ # Retrieves the given header values
65
+ #
66
+ # @param [Hash] header The header name.
67
+ # @return [Array] The Array of values for the header.
68
+ #
69
+ # @example
70
+ # # no example
71
+ #
72
+ def [](header)
73
+ @headers[header]
74
+ end
75
+
76
+
77
+ # Returns true if the current object's body is an IO instance
78
+ #
79
+ # @return [Boolean] True if it is an IO and false otherwise.
80
+ #
81
+ # @example
82
+ # is_io? #=> false
83
+ #
84
+ def is_io?
85
+ body.is_a?(IO) || body.is_a?(Net::ReadAdapter)
86
+ end
87
+
88
+
89
+ # Displays the current headers in a nice way
90
+ #
91
+ # @return [String]
92
+ #
93
+ # @example
94
+ # ec2.response.headers_info #=>
95
+ # 'content-type: "text/xml;charset=UTF-8", server: "AmazonEC2", something: ["a", "b"]'
96
+ #
97
+ def headers_info
98
+ @headers.to_s
99
+ end
100
+ end
101
+
102
+ end
103
+ end