stacker_bee 2.0.0 → 2.1.0.pre180

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +13 -5
  2. data/.travis.yml +13 -1
  3. data/README.md +134 -64
  4. data/bin/stacker_bee +4 -3
  5. data/config.default.yml +1 -1
  6. data/lib/stacker_bee/api.rb +2 -3
  7. data/lib/stacker_bee/client.rb +101 -19
  8. data/lib/stacker_bee/connection.rb +31 -16
  9. data/lib/stacker_bee/{middleware → http_middleware}/detokenizer.rb +3 -2
  10. data/lib/stacker_bee/http_middleware/graylog.rb +14 -0
  11. data/lib/stacker_bee/{middleware → http_middleware}/signed_query.rb +1 -1
  12. data/lib/stacker_bee/middleware/adapter.rb +16 -0
  13. data/lib/stacker_bee/middleware/base.rb +33 -0
  14. data/lib/stacker_bee/middleware/clean_response.rb +42 -0
  15. data/lib/stacker_bee/middleware/cloud_stack_api.rb +17 -0
  16. data/lib/stacker_bee/middleware/console_access.rb +38 -0
  17. data/lib/stacker_bee/middleware/de_namespace.rb +15 -0
  18. data/lib/stacker_bee/middleware/dictionary_flattener.rb +44 -0
  19. data/lib/stacker_bee/middleware/endpoint_normalizer.rb +23 -0
  20. data/lib/stacker_bee/middleware/environment.rb +23 -0
  21. data/lib/stacker_bee/middleware/format_keys.rb +13 -0
  22. data/lib/stacker_bee/middleware/format_values.rb +13 -0
  23. data/lib/stacker_bee/middleware/http_status.rb +14 -0
  24. data/lib/stacker_bee/middleware/json_body.rb +14 -0
  25. data/lib/stacker_bee/middleware/raise_on_http_error.rb +11 -0
  26. data/lib/stacker_bee/middleware/rashify_response.rb +21 -0
  27. data/lib/stacker_bee/middleware/remove_empty_strings.rb +11 -0
  28. data/lib/stacker_bee/middleware/remove_nils.rb +9 -0
  29. data/lib/stacker_bee/request_error.rb +8 -16
  30. data/lib/stacker_bee/utilities.rb +30 -0
  31. data/lib/stacker_bee/version.rb +1 -1
  32. data/lib/stacker_bee.rb +1 -1
  33. data/spec/cassettes/A_request_sent_to_CloudStack_for_console_access/returns_html_for_console_access.yml +33 -0
  34. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/{a_request_parameter_with_a_Map → a_request_parameter_with_a_map}/can_create_an_object.yml +0 -0
  35. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/a_request_that_triggers_an_error/properly_signs_the_request.yml +35 -0
  36. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/middleware/a_middleware_that_doesn_t_match_the_content_type/uses_the_middleware.yml +33 -0
  37. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/middleware/a_middleware_that_matches_the_content_type/uses_the_middleware.yml +33 -0
  38. data/spec/integration/check_spec.rb +48 -0
  39. data/spec/integration/configure_middleware_spec.rb +54 -16
  40. data/spec/integration/console_spec.rb +21 -0
  41. data/spec/integration/request_spec.rb +57 -2
  42. data/spec/spec_helper.rb +11 -1
  43. data/spec/units/faraday_graylog_middleware_spec.rb +1 -1
  44. data/spec/units/stacker_bee/client_spec.rb +47 -78
  45. data/spec/units/stacker_bee/connection_spec.rb +34 -11
  46. data/spec/units/stacker_bee/console_spec.rb +0 -0
  47. data/spec/units/stacker_bee/{graylog_faraday_middleware_spec.rb → http_middleware/graylog_spec.rb} +18 -2
  48. data/spec/units/stacker_bee/middleware/adapter_spec.rb +54 -0
  49. data/spec/units/stacker_bee/middleware/base_spec.rb +128 -0
  50. data/spec/units/stacker_bee/middleware/cloudstack_api_spec.rb +37 -0
  51. data/spec/units/stacker_bee/middleware/console_access_spec.rb +59 -0
  52. data/spec/units/stacker_bee/{dictionary_flattener_spec.rb → middleware/dictionary_flattener_spec.rb} +7 -7
  53. data/spec/units/stacker_bee/middleware/endpoint_normalizer_spec.rb +36 -0
  54. data/spec/units/stacker_bee/middleware/format_keys_spec.rb +18 -0
  55. data/spec/units/stacker_bee/middleware/format_values_spec.rb +15 -0
  56. data/spec/units/stacker_bee/middleware/http_status_spec.rb +34 -0
  57. data/spec/units/stacker_bee/middleware/raise_on_http_errors_spec.rb +5 -0
  58. data/spec/units/stacker_bee/middleware/remove_empty_strings_spec.rb +54 -0
  59. data/spec/units/stacker_bee/middleware/remove_nils_spec.rb +8 -0
  60. data/spec/units/stacker_bee/request_error_spec.rb +25 -37
  61. data/spec/units/stacker_bee/utilities_spec.rb +26 -0
  62. data/stacker_bee.gemspec +6 -2
  63. metadata +84 -38
  64. data/lib/stacker_bee/body_parser.rb +0 -23
  65. data/lib/stacker_bee/dictionary_flattener.rb +0 -41
  66. data/lib/stacker_bee/graylog_faraday_middleware.rb +0 -12
  67. data/lib/stacker_bee/middleware/logger.rb +0 -47
  68. data/lib/stacker_bee/request.rb +0 -46
  69. data/lib/stacker_bee/response.rb +0 -29
  70. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/a_request_parameter_with_a_Map/object.yml +0 -153
  71. data/spec/units/stacker_bee/middleware/logger_spec.rb +0 -55
  72. data/spec/units/stacker_bee/request_spec.rb +0 -51
  73. data/spec/units/stacker_bee/response_spec.rb +0 -79
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c61314ae91d1a5401971f178fc31e00cd9b06687
4
- data.tar.gz: 48d6f1e0eb6d767fdd42a3de0d1bfef4c6c30e22
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZjM2MmUzMTI4NjMzMTkxMjUxOTAzYzk0NzBlN2ZmNGJlNDQ3NjFjYw==
5
+ data.tar.gz: !binary |-
6
+ Yjg3ZGM0M2E3MTU2Yzg1M2Q4YmY3NDZhZjMzNjRiYzIyOWY2ZjhmOQ==
5
7
  SHA512:
6
- metadata.gz: 4acc5d7d764361a10298e3a2cbb95d877f474f3cd4790f7f5bb0799f59c86937bee7762f42e71a048a77894a05ad747b7ea5613b50396eddeec76e0dba4e6fff
7
- data.tar.gz: 2d1c306caec103c7b0464fb3d44dbd00571c00031f122acd2a80e83962220286b4a4d1c5498d6bfe8270558b295a41217f0524a6f93e9024bddb35fcb7f61263
8
+ metadata.gz: !binary |-
9
+ N2I1ODhiMTY2NWVhYmUxNzE4OWViNTVmZDY4MjgzM2QyOGI0MTkyZDdiNzY1
10
+ OTk1YjJiMGIzMDUyYmZlZDQ5NGY4OTQzYzNhMjExZjg5MDk4MDc3ZmZiNzg0
11
+ ZTEzZDAxNzdiOTcxMDM0OWM2ZmI1NGRkN2JmNzUxZWZhOGFmYjE=
12
+ data.tar.gz: !binary |-
13
+ ZGIzOWIyYWM0MjZhYTBjOTNiZjVhOWUxZGJiNDg2NDA0MTE3NTM5NDUyN2Jl
14
+ ZGU1NzFjMzRhOTUzOTRmNTg4NjQ5NmE0MDBjYWU0NDZlYmExYjk0NDdlZTQ3
15
+ MmM0OGZmNTY1MmJlMWRjNzEwOGNjNjc5OGU4OTM4ODViZTUyYzA=
data/.travis.yml CHANGED
@@ -1,9 +1,21 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.1.0
3
4
  - 2.0.0
4
5
  - 1.9.3
5
6
  - 1.9.2
6
7
  - jruby-19mode
7
- - rbx-19mode
8
8
  - ruby-head
9
9
  - jruby-head
10
+ matrix:
11
+ allow_failures:
12
+ - rvm: ruby-head
13
+ - rvm: jruby-head
14
+ deploy:
15
+ provider: rubygems
16
+ api_key:
17
+ secure: d5hC4iuoXOsrQKJRd9GSyZP2UlO2v9HsayhgNk2RlQQJFbqki6TcIlxN6j39/QER3pM3MUZyvCewtvxjrijt4f82xJGHRXaVNZvPSyD91PkGUtOuibLojc+GmwrzzRpu0d3TSEuzeKBSVag7AuJFO3oQnhEyblyZPyqWh1Ii1nM=
18
+ gem: stacker_bee
19
+ on:
20
+ repo: promptworks/stacker_bee
21
+ ruby: 2.1.0
data/README.md CHANGED
@@ -13,9 +13,11 @@ You can install StackerBee with rubygems:
13
13
 
14
14
  $ gem install stacker_bee
15
15
 
16
- If you are using Bundler simply add the following to your Gemfile:
16
+ If you are using Bundler add the following to your Gemfile:
17
17
 
18
- gem 'stacker_bee'
18
+ ```ruby
19
+ gem 'stacker_bee'
20
+ ```
19
21
 
20
22
  And execute:
21
23
 
@@ -24,35 +26,42 @@ And execute:
24
26
 
25
27
  ## Basic Usage
26
28
 
27
- cloud_stack = StackerBee::Client.new(
28
- url: 'http://localhost:8080/client/api',
29
- api_key: 'MY_API_KEY',
30
- secret_key: 'MY_SECRET_KEY'
31
- )
29
+ ```ruby
30
+ cloud_stack = StackerBee::Client.new(
31
+ url: 'http://localhost:8080/client/api',
32
+ api_key: 'MY_API_KEY',
33
+ secret_key: 'MY_SECRET_KEY'
34
+ )
32
35
 
33
- cloud_stack.list_virtual_machines state: 'Running'
34
- # => [ { id: '48b91ab4...', displayName: '...', ... },
35
- # { id: '59c02bc5...', displayName: '...', ... },
36
- # ... ]
36
+ cloud_stack.list_virtual_machines state: 'Running'
37
+ # => [ { id: '48b91ab4...', displayName: '...', ... },
38
+ # { id: '59c02bc5...', displayName: '...', ... },
39
+ # ... ]
37
40
 
38
- cloud_stack.create_volume name: 'MyVolume'
41
+ cloud_stack.create_volume name: 'MyVolume'
42
+ ```
39
43
 
40
44
  ## Features
41
45
 
42
46
  ### Idomatic Ruby formatting for names
43
47
 
44
- You can use `list_virtual_machines` instead of `listVirtualMachines` and
48
+ For example, you can use `list_virtual_machines` instead of `listVirtualMachines` and
45
49
  `affinity_group_id` instead of `affinitygroupid` (if you want to).
46
50
 
47
51
  For example:
48
52
 
49
- vm = cloud_stack.list_virtual_machines(affinity_group_id: id).first
50
- puts vm[:iso_display_text]
53
+ ```ruby
54
+ vm = cloud_stack.list_virtual_machines(affinity_group_id: id).first
55
+ puts vm[:iso_display_text]
56
+ ```
51
57
 
52
58
  ### Handling 'map' parameters
53
59
 
54
- For any endpoint requiring a map parameter, simply pass in a hash.
55
- create_tags(tags: { type: 'community'}, resource_type: "Template", resource_ids: id )
60
+ For any endpoint requiring a map parameter, pass in a hash.
61
+
62
+ ```ruby
63
+ cloud_stack.create_tags(tags: { type: 'community' }, resource_type: "Template", resource_ids: id )
64
+ ```
56
65
 
57
66
  This will yield a request with the following query string:
58
67
 
@@ -63,7 +72,9 @@ This will yield a request with the following query string:
63
72
  By default, StackerBee uses the CloudStack 4.2 API, but it doesn't have to.
64
73
  Use a different API version by setting the `api_path` configuration option to the path of a JSON file containing the response from your CloudStack instance's `listApis` command.
65
74
 
66
- StackerBee::Client.api_path = '/path/to/your/listApis/response.json'
75
+ ```ruby
76
+ StackerBee::Client.api_path = '/path/to/your/listApis/response.json'
77
+ ```
67
78
 
68
79
  ### CloudStack REPL
69
80
 
@@ -86,64 +97,106 @@ Example:
86
97
 
87
98
  Configuring a client:
88
99
 
89
- cloud_stack = StackerBee::Client.new(
90
- url: 'http://localhost:8080/client/api'
91
- api_key: 'API_KEY',
92
- secret_key: 'SECRET_KEY',
93
- logger: Rails.logger
94
- )
100
+ ```ruby
101
+ cloud_stack = StackerBee::Client.new(
102
+ url: 'http://localhost:8080/client/api',
103
+ api_key: 'API_KEY',
104
+ secret_key: 'SECRET_KEY'
105
+ )
106
+ ```
95
107
 
96
108
  All configuration parameters set on the `StackerBee::Client` class are used as defaults for `StackerBee::Client` instances.
97
109
 
98
- StackerBee::Client.url = 'http://localhost:8080/client/api'
99
- StackerBee::Client.logger = Rails.logger
110
+ ```ruby
111
+ StackerBee::Client.url = 'http://localhost:8080/client/api'
100
112
 
101
- user_client = StackerBee::Client.new(
102
- api_key: 'USER_API_KEY',
103
- secret_key: 'USER_SECRET_KEY'
104
- )
113
+ user_client = StackerBee::Client.new(
114
+ api_key: 'USER_API_KEY',
115
+ secret_key: 'USER_SECRET_KEY'
116
+ )
105
117
 
106
- root_client = StackerBee::Client.new(
107
- api_key: 'ROOT_API_KEY',
108
- secret_key: 'ROOT_SECRET_KEY'
109
- )
118
+ root_client = StackerBee::Client.new(
119
+ api_key: 'ROOT_API_KEY',
120
+ secret_key: 'ROOT_SECRET_KEY'
121
+ )
122
+ ```
110
123
 
111
124
  ### URL
112
125
 
113
126
  The URL of your CloudStack instance's URL.
114
127
 
115
- StackerBee::Client.url = 'http://localhost:8080/client/api'
128
+ ```ruby
129
+ StackerBee::Client.url = 'http://localhost:8080/client/api'
130
+ ```
116
131
 
117
132
  Or:
118
133
 
119
- my_client = StackerBee::Client.new(
120
- url: 'http://localhost:8080/client/api'
121
- )
134
+ ```ruby
135
+ my_client = StackerBee::Client.new(
136
+ url: 'http://localhost:8080/client/api'
137
+ )
138
+ ```
122
139
 
123
140
  ### Keys
124
141
 
125
142
  Your CloudStack credentials, i.e. API key and secret key.
126
143
 
127
- StackerBee::Client.api_key = 'MY_API_KEY'
128
- StackerBee::Client.secret_key = 'MY_SECRET_KEY'
144
+ ```ruby
145
+ StackerBee::Client.api_key = 'MY_API_KEY'
146
+ StackerBee::Client.secret_key = 'MY_SECRET_KEY'
147
+ ```
129
148
 
130
149
  Or:
131
150
 
132
- my_client = StackerBee::Client.new(
133
- api_key: 'MY_API_KEY',
134
- secret_key: 'MY_SECRET_KEY'
135
- )
151
+ ```ruby
152
+ my_client = StackerBee::Client.new(
153
+ api_key: 'MY_API_KEY',
154
+ secret_key: 'MY_SECRET_KEY'
155
+ )
156
+ ```
157
+
158
+ ### Middleware
159
+
160
+ StackerBee can be configured with middleware. It uses it's own middleware stack to implement some of its functionality.
161
+
162
+ To add a middleware, use the `middlewares` configuration option. For example:
163
+
164
+ ```ruby
165
+ class StdoutLoggingMiddleware < StackerBee::Middleware::Base
166
+ def call(env)
167
+ app.call(env)
168
+ p "CloudStack call: #{env.inspect}"
169
+ end
170
+ end
171
+
172
+ class PrependedMiddleware < StackerBee::Middleware::Base
173
+ def call(env) app.call(env) end
174
+ end
175
+
176
+ StackerBee::Client.configuration = {
177
+ middlewares: ->(builder) do
178
+ # Using `before` places the middleware before the default middlewares
179
+ builder.before PrependedMiddleware
180
+
181
+ # Using `use` places the middleware after the default middlewares,
182
+ # but before the request is sent to Faraday
183
+ builder.use StdoutLoggingMiddleware
184
+ end
185
+ }
186
+ ```
136
187
 
137
188
  ### Faraday Middleware
138
189
 
139
- StackerBee is built on [Faraday](https://github.com/lostisland/faraday) and makes it easy for you to add Faraday middleware. Here's an example of adding your own middleware.
190
+ StackerBee is built on [Faraday](https://github.com/lostisland/faraday) and allows you to add Faraday middleware. Here's an example of adding your own middleware.
140
191
 
141
- StackerBee::Client.default_config = {
142
- middlewares: ->(faraday) do
143
- faraday.use Custom::LoggingMiddleware, Logger.new
144
- faraday.use Custom::CachingMiddleware, Rails.cache
145
- end
146
- }
192
+ ```ruby
193
+ StackerBee::Client.configuration = {
194
+ faraday_middlewares: ->(faraday) do
195
+ faraday.use Custom::LoggingMiddleware, Logger.new
196
+ faraday.use Custom::CachingMiddleware, Rails.cache
197
+ end
198
+ }
199
+ ```
147
200
 
148
201
  StackerBee itself puts some middlewares on Faraday. Any middlewares you add will be placed after these. If you want your middleware to come as the very first, you can use Faraday's builder like `faraday.builder.insert 0, MyMiddleware`.
149
202
 
@@ -155,30 +208,35 @@ Logging is best handled with Faraday middleware.
155
208
 
156
209
  If you're using the Graylog2 GELF format, you're in luck because StackerBee currently ships with a Faraday middleware for that. Here's an example of logging to Graylog2:
157
210
 
158
- logger = GELF::Notifier.new("localhost", 12201)
211
+ ```ruby
212
+ logger = GELF::Notifier.new("localhost", 12201)
159
213
 
160
- StackerBee::Client.default_config = {
161
- middlewares: ->(faraday) { faraday.use faraday.use StackerBee::GraylogFaradayMiddleware, logger }
162
- }
214
+ StackerBee::Client.configuration = {
215
+ faraday_middlewares: ->(faraday) { faraday.use faraday.use StackerBee::GraylogFaradayMiddleware, logger }
216
+ }
217
+ ```
163
218
 
164
219
  #### Basic logging
165
220
 
166
221
  To log to a file or STDOUT, Faraday has a built-in logger. You can use it like so:
167
222
 
168
- StackerBee::Client.default_config = {
169
- middlewares: ->(faraday) { faraday.response :logger }
170
- }
223
+ ```ruby
224
+ StackerBee::Client.configuration = {
225
+ faraday_middlewares: ->(faraday) { faraday.response :logger }
226
+ }
227
+ ```
171
228
 
172
229
  ### Bulk Configuration
173
230
 
174
231
  The `StackerBee::Client` class can be configured with multiple options at once.
175
232
 
176
- StackerBee::Client.default_config = {
177
- url: 'http://localhost:8080/client/api',
178
- logger: Rails.logger,
179
- api_key: 'API_KEY',
180
- secret_key: 'MY_SECRET_KEY'
181
- }
233
+ ```ruby
234
+ StackerBee::Client.configuration = {
235
+ url: 'http://localhost:8080/client/api',
236
+ api_key: 'API_KEY',
237
+ secret_key: 'MY_SECRET_KEY'
238
+ }
239
+ ```
182
240
 
183
241
  ## Contributing
184
242
 
@@ -202,6 +260,18 @@ This project uses [Rubocop](https://github.com/bbatsov/rubocop) to enforce code
202
260
 
203
261
  $ bundle exec rubocop
204
262
 
263
+ ### Releasing
264
+
265
+ To create a release, first bump the version in `lib/stacker_bee/version.rb`, and commit. Then, build the gem and release it to Rubygems with `rake release`:
266
+
267
+ $ rake release
268
+ stacker_bee 1.2.3 built to pkg/stacker_bee-1.2.3.gem.
269
+ Tagged v1.2.3.
270
+ Pushed git commits and tags.
271
+ Pushed stacker_bee 1.2.3 to rubygems.org.
272
+
273
+ We use Bundler's gem tasks to manage releases. See the output of `rake -T` and [Bundler's Rubygems documentation](http://bundler.io/rubygems.html) for more information.
274
+
205
275
  ## Thanks to
206
276
 
207
277
  - [Chip Childers](http://github.com/chipchilders) for a [reference implementation of a CloudStack client in Ruby](http://chipchilders.github.io/cloudstack_ruby_client/)
data/bin/stacker_bee CHANGED
@@ -1,4 +1,7 @@
1
1
  #! /usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
4
+
2
5
  require 'optparse'
3
6
  require 'stacker_bee'
4
7
  require 'json'
@@ -54,7 +57,7 @@ begin
54
57
  options.merge! YAML.load(hash)
55
58
  end
56
59
  unless (%w(api_key secret_key url) - options.keys).empty?
57
- puts "Please specify a config file or all of the following: " +
60
+ puts "Please specify a config file or all of the following: " \
58
61
  "--api_key, --secret_key and --url"
59
62
  exit
60
63
  end
@@ -68,8 +71,6 @@ if verbose
68
71
  puts "StackerBee version #{StackerBee::VERSION}"
69
72
  puts "URL: #{options["url"]}"
70
73
  puts "API key: #{options["api_key"]}"
71
- else
72
- options['logger'] = Logger.new('/dev/null')
73
74
  end
74
75
 
75
76
  client = StackerBee::Client.new(options)
data/config.default.yml CHANGED
@@ -1,3 +1,3 @@
1
- url: 'http://127.0.0.1:1234/client/api'
1
+ url: 'http://127.0.0.1:1234/client/api/'
2
2
  api_key: 'MY-KEY-MY-KEY-MY-KEY-MY-KEY-MY-KEY-MY-KEY-MY-KEY-MY-KEY-MY-KEY-MY-KEY-MY-KEY-MY-KEY-MY'
3
3
  secret_key: 'TOP-SECRET-TOP-SECRET-TOP-SECRET-TOP-SECRET-TOP-SECRET-TOP-SECRET-TOP-SECRET-TOP-SECRE'
@@ -9,8 +9,7 @@ module StackerBee
9
9
 
10
10
  def initialize(attrs = {})
11
11
  attrs.each_pair do |key, value|
12
- setter = "#{key}="
13
- send(setter, value)
12
+ send "#{key}=", value
14
13
  end
15
14
  end
16
15
 
@@ -19,7 +18,7 @@ module StackerBee
19
18
  end
20
19
 
21
20
  def key?(key)
22
- endpoints.key? uncase(key)
21
+ endpoints.key?(uncase(key))
23
22
  end
24
23
 
25
24
  protected
@@ -2,9 +2,23 @@ require "forwardable"
2
2
  require "stacker_bee/configuration"
3
3
  require "stacker_bee/api"
4
4
  require "stacker_bee/connection"
5
- require "stacker_bee/request"
6
- require "stacker_bee/dictionary_flattener"
7
- require "stacker_bee/response"
5
+ require "stacker_bee/middleware/environment"
6
+ require "stacker_bee/middleware/base"
7
+ require "stacker_bee/middleware/adapter"
8
+ require "stacker_bee/middleware/endpoint_normalizer"
9
+ require "stacker_bee/middleware/remove_empty_strings"
10
+ require "stacker_bee/middleware/cloud_stack_api"
11
+ require "stacker_bee/middleware/dictionary_flattener"
12
+ require "stacker_bee/middleware/remove_nils"
13
+ require "stacker_bee/middleware/format_keys"
14
+ require "stacker_bee/middleware/format_values"
15
+ require "stacker_bee/middleware/json_body"
16
+ require "stacker_bee/middleware/de_namespace"
17
+ require "stacker_bee/middleware/rashify_response"
18
+ require "stacker_bee/middleware/clean_response"
19
+ require "stacker_bee/middleware/raise_on_http_error"
20
+ require "stacker_bee/middleware/http_status"
21
+ require "stacker_bee/middleware/console_access"
8
22
 
9
23
  module StackerBee
10
24
  class Client
@@ -21,6 +35,58 @@ module StackerBee
21
35
  :secret_key,
22
36
  :secret_key=
23
37
 
38
+ def middlewares
39
+ # request
40
+ builder.use Middleware::ConsoleAccess
41
+
42
+ builder.use Middleware::EndpointNormalizer, api: self.class.api
43
+ builder.use Middleware::RemoveEmptyStrings
44
+ builder.use Middleware::CloudStackAPI, api_key: configuration.api_key
45
+
46
+ configuration.middlewares.call builder
47
+
48
+ builder.use Middleware::DictionaryFlattener
49
+ builder.use Middleware::RemoveNils
50
+ builder.use Middleware::FormatKeys
51
+ builder.use Middleware::FormatValues
52
+
53
+ # response
54
+ builder.use Middleware::RaiseOnHTTPError
55
+ builder.use Middleware::HTTPStatus
56
+ builder.use Middleware::CleanResponse
57
+ builder.use Middleware::RashifyResponse
58
+ builder.use Middleware::DeNamespace
59
+ builder.use Middleware::JSONBody
60
+
61
+ builder.use Middleware::Adapter, connection: connection
62
+
63
+ builder.build
64
+ end
65
+
66
+ def builder
67
+ @builder ||= Builder.new
68
+ end
69
+
70
+ class Builder
71
+ attr_accessor :middlewares
72
+
73
+ def middlewares
74
+ @middlewares ||= []
75
+ end
76
+
77
+ def use(*middleware_definition)
78
+ middlewares << middleware_definition
79
+ end
80
+
81
+ def before(*middleware_definition)
82
+ middlewares.unshift middleware_definition
83
+ end
84
+
85
+ def build
86
+ middlewares.map { |klass, *args| klass.new(*args) }
87
+ end
88
+ end
89
+
24
90
  class << self
25
91
  def reset!
26
92
  @api, @api_path, @default_config = nil
@@ -28,8 +94,8 @@ module StackerBee
28
94
 
29
95
  def default_config
30
96
  @default_config ||= {
31
- allow_empty_string_params: false,
32
- middlewares: ->(*) {}
97
+ faraday_middlewares: proc {},
98
+ middlewares: proc {}
33
99
  }
34
100
  end
35
101
 
@@ -65,29 +131,45 @@ module StackerBee
65
131
  end
66
132
 
67
133
  def request(endpoint_name, params = {})
68
- request = Request.new(endpoint_for(endpoint_name), api_key, params)
69
- request.allow_empty_string_params =
70
- configuration.allow_empty_string_params
71
- raw_response = connection.get(request)
72
- Response.new(raw_response)
134
+ env = Middleware::Environment.new(
135
+ endpoint_name: endpoint_name,
136
+ api_key: api_key,
137
+ params: params
138
+ )
139
+
140
+ middleware_app.call(env)
141
+
142
+ env.response.body
73
143
  end
74
144
 
75
- def endpoint_for(name)
76
- api = self.class.api[name]
77
- api && api["name"]
145
+ def middleware_app
146
+ @app ||= begin
147
+ middleware_stack = middlewares
148
+
149
+ last_middleware = nil
150
+ middleware_stack.reverse.each do |middleware|
151
+ middleware.app = last_middleware
152
+ last_middleware = middleware
153
+ end
154
+
155
+ middleware_stack.first
156
+ end
78
157
  end
79
158
 
80
- def method_missing(name, *args, &block)
81
- endpoint = endpoint_for(name)
82
- if endpoint
83
- request(endpoint, *args, &block)
159
+ def method_missing(method_name, *args, &block)
160
+ if respond_to_via_delegation?(method_name)
161
+ request(method_name, *args, &block)
84
162
  else
85
163
  super
86
164
  end
87
165
  end
88
166
 
89
- def respond_to?(name, include_private = false)
90
- self.class.api.key?(name) || super
167
+ def respond_to?(method_name, include_private = false)
168
+ super || respond_to_via_delegation?(method_name)
169
+ end
170
+
171
+ def respond_to_via_delegation?(method_name)
172
+ !!middleware_app.endpoint_name_for(method_name)
91
173
  end
92
174
 
93
175
  protected
@@ -1,8 +1,7 @@
1
1
  require "faraday"
2
2
  require "uri"
3
- require "stacker_bee/middleware/signed_query"
4
- require "stacker_bee/middleware/logger"
5
- require "stacker_bee/middleware/detokenizer"
3
+ require "stacker_bee/http_middleware/signed_query"
4
+ require "stacker_bee/http_middleware/detokenizer"
6
5
 
7
6
  module StackerBee
8
7
  class ConnectionError < StandardError
@@ -13,26 +12,42 @@ module StackerBee
13
12
 
14
13
  def initialize(configuration)
15
14
  @configuration = configuration
16
- uri = URI.parse(self.configuration.url)
17
- @path = uri.path
15
+
16
+ uri = URI.parse(self.configuration.url)
18
17
  uri.path = ''
19
18
  fail ConnectionError, "no protocol specified" unless uri.scheme
20
- initialize_faraday(uri)
19
+
20
+ ssl_verify = !configuration.ssl_verify.nil? ?
21
+ configuration.ssl_verify : true
22
+
23
+ initialize_faraday(url: uri.to_s,
24
+ ssl: { verify: ssl_verify })
25
+ end
26
+
27
+ def initialize_faraday(options)
28
+ @faraday = Faraday.new(options) do |faraday|
29
+ faraday.use HTTPMiddleware::Detokenizer
30
+ faraday.use HTTPMiddleware::SignedQuery, configuration.secret_key
31
+
32
+ configuration.faraday_middlewares.call faraday
33
+
34
+ unless has_adapter?(faraday.builder.handlers)
35
+ faraday.adapter Faraday.default_adapter # Net::HTTP
36
+ end
37
+ end
21
38
  end
22
39
 
23
- def initialize_faraday(uri)
24
- @faraday = Faraday.new(url: uri.to_s) do |faraday|
25
- faraday.use Middleware::Detokenizer
26
- faraday.use Middleware::SignedQuery, configuration.secret_key
27
- configuration.middlewares.call faraday
28
- faraday.adapter Faraday.default_adapter # Net::HTTP
40
+ def has_adapter?(handlers)
41
+ handlers.detect do |handler|
42
+ handler.klass.ancestors.include?(Faraday::Adapter)
29
43
  end
30
44
  end
31
45
 
32
- def get(request)
33
- @faraday.get(@path, request.query_params)
34
- rescue Faraday::Error::ConnectionFailed
35
- raise ConnectionError, "Failed to connect to #{configuration.url}"
46
+ def get(params, path)
47
+ @faraday.get(path, params)
48
+ rescue Faraday::Error::ConnectionFailed => error
49
+ raise ConnectionError,
50
+ "Failed to connect to #{configuration.url}, #{error}"
36
51
  end
37
52
  end
38
53
  end
@@ -2,7 +2,7 @@ require "faraday"
2
2
  require "base64"
3
3
 
4
4
  module StackerBee
5
- module Middleware
5
+ module HTTPMiddleware
6
6
  class Detokenizer < Faraday::Middleware
7
7
  def call(env)
8
8
  detokenize(env[:url])
@@ -10,7 +10,8 @@ module StackerBee
10
10
  end
11
11
 
12
12
  def detokenize(uri)
13
- uri.query = StackerBee::DictionaryFlattener.detokenize uri.query
13
+ uri.query =
14
+ StackerBee::Middleware::DictionaryFlattener.detokenize uri.query
14
15
  end
15
16
  end
16
17
  end
@@ -0,0 +1,14 @@
1
+ module StackerBee
2
+ module HTTPMiddleware
3
+ class Graylog < FaradayMiddleware::Graylog
4
+ def facility
5
+ 'stacker-bee'
6
+ end
7
+
8
+ def short_message(env)
9
+ message = env[:url].query.scan(/&command=([^&]*)/).join(' ')
10
+ "StackerBee #{message}".strip
11
+ end
12
+ end
13
+ end
14
+ end
@@ -2,7 +2,7 @@ require "faraday"
2
2
  require "base64"
3
3
 
4
4
  module StackerBee
5
- module Middleware
5
+ module HTTPMiddleware
6
6
  class SignedQuery < Faraday::Middleware
7
7
  def initialize(app, key)
8
8
  @key = key
@@ -0,0 +1,16 @@
1
+ module StackerBee
2
+ module Middleware
3
+ class Adapter < Base
4
+ def call(env)
5
+ params = env.request.params.to_a.sort
6
+ env.raw_response = connection.get(params, env.request.path)
7
+ env.response.content_type =
8
+ env.raw_response.env[:response_headers]["content-type"]
9
+ env.response.body = env.raw_response.body
10
+ end
11
+
12
+ def endpoint_name_for(*)
13
+ end
14
+ end
15
+ end
16
+ end