stacker_bee 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +7 -0
  5. data/.travis.yml +9 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +112 -0
  9. data/Rakefile +6 -0
  10. data/bin/stacker_bee +97 -0
  11. data/config.default.yml +3 -0
  12. data/config/4.2.json +67126 -0
  13. data/lib/stacker_bee.rb +16 -0
  14. data/lib/stacker_bee/api.rb +44 -0
  15. data/lib/stacker_bee/body_parser.rb +23 -0
  16. data/lib/stacker_bee/client.rb +105 -0
  17. data/lib/stacker_bee/configuration.rb +6 -0
  18. data/lib/stacker_bee/connection.rb +31 -0
  19. data/lib/stacker_bee/middleware/logger.rb +48 -0
  20. data/lib/stacker_bee/middleware/signed_query.rb +29 -0
  21. data/lib/stacker_bee/rash.rb +95 -0
  22. data/lib/stacker_bee/request.rb +41 -0
  23. data/lib/stacker_bee/request_error.rb +39 -0
  24. data/lib/stacker_bee/response.rb +29 -0
  25. data/lib/stacker_bee/utilities.rb +18 -0
  26. data/lib/stacker_bee/version.rb +3 -0
  27. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/.yml +33 -0
  28. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/a_nil_request_parameter/properly_executes_the_request.yml +33 -0
  29. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/a_request_parameter_with_an_Array/.yml +35 -0
  30. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/a_request_parameter_with_and_empty_string/properly_executes_the_request.yml +33 -0
  31. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/containing_an_error/.yml +37 -0
  32. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/containing_an_error/should_log_response_as_error.yml +37 -0
  33. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/first/.yml +33 -0
  34. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/first_item/.yml +33 -0
  35. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/first_item/_account_type_/.yml +33 -0
  36. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/first_item/_accounttype_/.yml +33 -0
  37. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/should_log_request.yml +33 -0
  38. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/should_not_log_response_as_error.yml +33 -0
  39. data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/space_character_in_a_request_parameter/properly_signs_the_request.yml +32 -0
  40. data/spec/fixtures/4.2.json +67126 -0
  41. data/spec/fixtures/simple.json +871 -0
  42. data/spec/integration/request_spec.rb +116 -0
  43. data/spec/spec_helper.rb +58 -0
  44. data/spec/units/stacker_bee/api_spec.rb +24 -0
  45. data/spec/units/stacker_bee/client_spec.rb +181 -0
  46. data/spec/units/stacker_bee/configuration_spec.rb +7 -0
  47. data/spec/units/stacker_bee/connection_spec.rb +45 -0
  48. data/spec/units/stacker_bee/middleware/logger_spec.rb +55 -0
  49. data/spec/units/stacker_bee/rash_spec.rb +87 -0
  50. data/spec/units/stacker_bee/request_error_spec.rb +44 -0
  51. data/spec/units/stacker_bee/request_spec.rb +49 -0
  52. data/spec/units/stacker_bee/response_spec.rb +79 -0
  53. data/spec/units/stacker_bee/utilities_spec.rb +25 -0
  54. data/spec/units/stacker_bee_spec.rb +6 -0
  55. data/stacker_bee.gemspec +30 -0
  56. metadata +225 -0
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+ require 'logger'
3
+
4
+ describe "A response to a request sent to the CloudStack API", :vcr do
5
+ let(:io) { StringIO.new }
6
+ let(:logger) { Logger.new(io) }
7
+ let(:log_string) { io.string }
8
+ let(:url) { CONFIG["url"] }
9
+ let(:config_hash) do
10
+ {
11
+ url: url,
12
+ api_key: CONFIG["api_key"],
13
+ secret_key: CONFIG["secret_key"],
14
+ logger: logger,
15
+ apis_path: File.join(File.dirname(__FILE__), '../fixtures/4.2.json')
16
+ }
17
+ end
18
+ let(:client) do
19
+ StackerBee::Client.new(config_hash)
20
+ end
21
+ subject { client.list_accounts }
22
+
23
+ it { should_not be_empty }
24
+
25
+ context "first item" do
26
+ subject { client.list_accounts.first }
27
+ it { should include "id" }
28
+ its(["accounttype"]) { should be_a Numeric }
29
+ its(["account_type"]) { should be_a Numeric }
30
+ end
31
+
32
+ it "should log request" do
33
+ subject
34
+ log_string.should include "listAccounts"
35
+ end
36
+
37
+ it "should not log response as error" do
38
+ subject
39
+ log_string.should_not include "ERROR"
40
+ end
41
+
42
+ context "containing an error" do
43
+ subject do
44
+ client.deploy_virtual_machine
45
+ end
46
+ it { expect { subject }.to raise_error StackerBee::ClientError }
47
+
48
+ it "should log response as error" do
49
+ begin
50
+ subject
51
+ rescue StackerBee::ClientError
52
+ log_string.should include "ERROR"
53
+ end
54
+ end
55
+ end
56
+
57
+ context "failing to connect" do
58
+ let(:url) { "http://127.0.0.1:1234/client/api" }
59
+ it "should raise helpful exception" do
60
+ klass = StackerBee::ConnectionError
61
+ expect(-> { subject }).to raise_error klass, /#{url}/
62
+ end
63
+ end
64
+
65
+ context "trailing slash in URL", :regression do
66
+ let(:url) { CONFIG["url"].gsub(/\/$/, '') + '/' }
67
+ it "makes request with trailing slash" do
68
+ stub = stub_request(:get, /#{url}/).to_return(body: '{"foo": {}}')
69
+ subject
70
+ stub.should have_been_requested
71
+ end
72
+ end
73
+
74
+ context "space character in a request parameter", :regression do
75
+ let(:params) { { name: "stacker bee" } }
76
+ subject do
77
+ client.list_accounts(params)
78
+ end
79
+
80
+ it "properly signs the request" do
81
+ expect { subject }.not_to raise_error
82
+ end
83
+ end
84
+
85
+ context "a nil request parameter", :regression do
86
+ let(:params) { { name: nil } }
87
+ subject do
88
+ client.list_accounts(params)
89
+ end
90
+
91
+ it "properly executes the request" do
92
+ expect { subject }.not_to raise_error
93
+ end
94
+ end
95
+
96
+ context "a request parameter with and empty string", :regression do
97
+ let(:params) { { name: '' } }
98
+ subject do
99
+ client.list_accounts(params)
100
+ end
101
+
102
+ it "properly executes the request" do
103
+ expect { subject }.not_to raise_error
104
+ end
105
+ end
106
+
107
+ context "a request parameter with an Array", :regression do
108
+ let(:params) { { page: 1, pagesize: 1, details: [:events, :stats] } }
109
+ subject do
110
+ client.list_hosts(params).first.keys
111
+ end
112
+ it { should include "cpuused" }
113
+ it { should include "events" }
114
+ it { should_not include "cpuallocated" }
115
+ end
116
+ end
@@ -0,0 +1,58 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ require File.expand_path("../../lib/stacker_bee", __FILE__)
9
+
10
+ require 'yaml'
11
+
12
+ default_config_file = File.expand_path("../../config.default.yml", __FILE__)
13
+ config_file = File.expand_path("../../config.yml", __FILE__)
14
+
15
+ CONFIG = YAML.load(File.read(default_config_file))
16
+ CONFIG.merge!(YAML.load(File.read(config_file))) if File.exists?(config_file)
17
+
18
+ require 'webmock/rspec'
19
+
20
+ RSpec.configure do |config|
21
+ config.treat_symbols_as_metadata_keys_with_true_values = true
22
+ config.run_all_when_everything_filtered = true
23
+ config.filter_run :focus
24
+
25
+ # Run specs in random order to surface order dependencies. If you find an
26
+ # order dependency and want to debug it, you can fix the order by providing
27
+ # the seed, which is printed after each run.
28
+ # --seed 1234
29
+ config.order = 'random'
30
+
31
+ config.after(:each) do
32
+ StackerBee::Client.reset!
33
+ end
34
+ end
35
+
36
+ require 'vcr'
37
+
38
+ VCR.configure do |c|
39
+ c.hook_into :webmock
40
+ c.cassette_library_dir = 'spec/cassettes'
41
+ c.filter_sensitive_data('<CLOUD_STACK_URL>') do
42
+ CONFIG["url"]
43
+ end
44
+ c.filter_sensitive_data('<CLOUD_STACK_API_KEY>') do
45
+ CONFIG["api_key"]
46
+ end
47
+ c.filter_sensitive_data('<CLOUD_STACK_SECRET_KEY>') do
48
+ CONFIG["secret_key"]
49
+ end
50
+ c.default_cassette_options = {
51
+ record: :new_episodes,
52
+ match_requests_on: [
53
+ :method,
54
+ VCR.request_matchers.uri_without_param(:signature)
55
+ ]
56
+ }
57
+ c.configure_rspec_metadata!
58
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ describe StackerBee::API do
4
+ subject { api }
5
+
6
+ let(:api) { StackerBee::API.new(api_path: api_path) }
7
+ let(:api_path) do
8
+ File.join(File.dirname(__FILE__), '../../fixtures/simple.json')
9
+ end
10
+
11
+ its(:api_path) { should eq api_path }
12
+
13
+ its(["list_virtual_machines"]) { should be_a Hash }
14
+ its(["get_vm_password"]) { should be_a Hash }
15
+ its(["getvmpassword"]) { should be_a Hash }
16
+ its(["getVMPassword"]) { should be_a Hash }
17
+ its(["getVmPassword"]) { should be_a Hash }
18
+ its(["getWRONG"]) { should be_nil }
19
+
20
+ it { should be_key "get_vm_password" }
21
+ it { should be_key "getvmpassword" }
22
+ it { should be_key "getVMPassword" }
23
+ it { should be_key "getVmPassword" }
24
+ end
@@ -0,0 +1,181 @@
1
+ require "spec_helper"
2
+ require "ostruct"
3
+
4
+ describe StackerBee::Client, ".api" do
5
+ let(:api) { double }
6
+ before do
7
+ StackerBee::API.stub(:new) { api }
8
+ StackerBee::Client.api_path = nil
9
+ end
10
+ subject { StackerBee::Client }
11
+ its(:api_path) { should_not be_nil }
12
+ its(:api) { should eq api }
13
+ end
14
+
15
+ describe StackerBee::Client, "calling endpoint" do
16
+ let(:url) { "cloud-stack.com" }
17
+ let(:api_key) { "cloud-stack-api-key" }
18
+ let(:secret_key) { "cloud-stack-secret-key" }
19
+ let(:config_hash) do
20
+ {
21
+ url: url,
22
+ api_key: api_key,
23
+ secret_key: secret_key
24
+ }
25
+ end
26
+ let(:client) { StackerBee::Client.new config_hash }
27
+ let(:endpoint) { :list_virtual_machines }
28
+ let(:params) { { list: :all } }
29
+ let(:connection) { double }
30
+ let(:request) { double :allow_empty_string_params= => nil }
31
+ let(:raw_response) { double }
32
+ let(:response) { double }
33
+ let(:api_path) do
34
+ File.join(File.dirname(__FILE__), '../../fixtures/simple.json')
35
+ end
36
+
37
+ before do
38
+ StackerBee::Client.api_path = api_path
39
+ StackerBee::Connection.stub(:new) { connection }
40
+ StackerBee::Request.stub(:new).with(
41
+ "listVirtualMachines", api_key, params
42
+ ) do
43
+ request
44
+ end
45
+ connection.stub(:get).with(request) { raw_response }
46
+ StackerBee::Response.stub(:new).with(raw_response) { response }
47
+ end
48
+
49
+ subject { client }
50
+ it { should respond_to endpoint }
51
+ describe "response to endpoint request" do
52
+ subject { client.list_virtual_machines(params) }
53
+ it { should eq response }
54
+ end
55
+ end
56
+
57
+ describe StackerBee::Client, "#request" do
58
+ subject { client.request(endpoint, params) }
59
+ let(:endpoint) { "listVirtualMachines" }
60
+ let(:params) { { list: :all } }
61
+
62
+ let(:url) { "cloud-stack.com" }
63
+ let(:api_key) { "cloud-stack-api-key" }
64
+ let(:secret_key) { "cloud-stack-secret-key" }
65
+ let(:config_hash) do
66
+ {
67
+ url: url,
68
+ api_key: api_key,
69
+ secret_key: secret_key
70
+ }
71
+ end
72
+ let(:client) { StackerBee::Client.new config_hash }
73
+ let(:connection) { double }
74
+ let(:request) { double :allow_empty_string_params= => nil }
75
+ let(:raw_response) { double }
76
+ let(:response) { double }
77
+
78
+ before do
79
+ StackerBee::Connection.should_receive(:new) { connection }
80
+ StackerBee::Request.should_receive(:new).with(endpoint, api_key, params) do
81
+ request
82
+ end
83
+ connection.should_receive(:get).with(request) { raw_response }
84
+ StackerBee::Response.should_receive(:new).with(raw_response) { response }
85
+ end
86
+
87
+ it { should eq response }
88
+
89
+ context "called with a differently-cased endpoint" do
90
+ subject { client.request("list_Virtual_mACHINES", params) }
91
+ it { should eq response }
92
+ end
93
+ end
94
+
95
+ describe StackerBee::Client, "configuration" do
96
+ let(:default_url) { "default_cloud-stack.com" }
97
+ let(:default_api_key) { "default-cloud-stack-api-key" }
98
+ let(:default_secret_key) { "default-cloud-stack-secret-key" }
99
+ let(:default_config_hash) do
100
+ {
101
+ url: default_url,
102
+ api_key: default_api_key,
103
+ secret_key: default_secret_key,
104
+ allow_empty_string_params: false
105
+ }
106
+ end
107
+ let!(:default_configuration) do
108
+ StackerBee::Configuration.new(default_config_hash)
109
+ end
110
+ let(:instance_url) { "instance-cloud-stack.com" }
111
+ let(:instance_api_key) { "instance-cloud-stack-api-key" }
112
+ let(:instance_secret_key) { "instance-cloud-stack-secret-key" }
113
+ let(:instance_config_hash) do
114
+ {
115
+ url: instance_url,
116
+ api_key: instance_api_key,
117
+ secret_key: instance_secret_key,
118
+ allow_empty_string_params: false
119
+ }
120
+ end
121
+ let!(:instance_configuration) do
122
+ StackerBee::Configuration.new(instance_config_hash)
123
+ end
124
+ before do
125
+ StackerBee::Configuration.stub(:new) do
126
+ fail "Unexpected Configuration instantiation"
127
+ end
128
+ StackerBee::Configuration.stub(:new).with(default_config_hash) do
129
+ default_configuration
130
+ end
131
+ StackerBee::Configuration.stub(:new).with(instance_config_hash) do
132
+ instance_configuration
133
+ end
134
+ StackerBee::Client.configuration = default_config_hash
135
+ end
136
+
137
+ describe ".new" do
138
+ subject { StackerBee::Client.new }
139
+
140
+ context "with default, class configuration" do
141
+ its(:url) { should eq default_url }
142
+ its(:api_key) { should eq default_api_key }
143
+ its(:secret_key) { should eq default_secret_key }
144
+ end
145
+
146
+ context "with instance-specific configuration" do
147
+ subject { StackerBee::Client.new(instance_config_hash) }
148
+ its(:configuration) { should eq instance_configuration }
149
+ its(:url) { should eq instance_url }
150
+ its(:api_key) { should eq instance_api_key }
151
+ its(:secret_key) { should eq instance_secret_key }
152
+ end
153
+
154
+ context "with instance-specific configuration that's not a hash" do
155
+ subject { StackerBee::Client.new(config) }
156
+ let(:config) { double(to_hash: instance_config_hash) }
157
+ its(:configuration) { should eq instance_configuration }
158
+ its(:url) { should eq instance_url }
159
+ its(:api_key) { should eq instance_api_key }
160
+ its(:secret_key) { should eq instance_secret_key }
161
+ end
162
+
163
+ describe "#url" do
164
+ let(:other_url) { "other-cloud-stack.com" }
165
+ before { subject.url = other_url }
166
+ its(:url) { should eq other_url }
167
+ end
168
+
169
+ describe "#api_key" do
170
+ let(:other_api_key) { "other-cloud-stack-api-key" }
171
+ before { subject.api_key = other_api_key }
172
+ its(:api_key) { should eq other_api_key }
173
+ end
174
+
175
+ describe "#secret_key" do
176
+ let(:other_secret_key) { "other-cloud-stack-secret-key" }
177
+ before { subject.secret_key = other_secret_key }
178
+ its(:secret_key) { should eq other_secret_key }
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,7 @@
1
+ require "spec_helper"
2
+
3
+ describe StackerBee::Configuration do
4
+ its(:url) { should be_nil }
5
+ its(:api_key) { should be_nil }
6
+ its(:secret_key) { should be_nil }
7
+ end
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ describe StackerBee::Connection do
4
+ let(:url) { "http://test.com:1234/foo/bar/" }
5
+ let(:secret_key) { "shhh" }
6
+ let(:configuration) { double url: url, secret_key: secret_key }
7
+ let(:query_params) { [[:foo, :bar]] }
8
+ let(:request) { double query_params: query_params }
9
+ let(:response) { double }
10
+ let(:faraday) { double get: response }
11
+ let(:connection) { StackerBee::Connection.new configuration }
12
+ subject { connection.get request }
13
+ before do
14
+ Faraday.stub(:new) { faraday }
15
+ end
16
+
17
+ context "successfully connecting" do
18
+ before do
19
+ faraday.should_receive(:get).with('/foo/bar/', query_params) { response }
20
+ end
21
+ it { should be response }
22
+ it "specifies url without path when creating connection" do
23
+ subject
24
+ Faraday.should have_received(:new).with(url: "http://test.com:1234")
25
+ end
26
+ end
27
+
28
+ context "failing to connect" do
29
+ before do
30
+ faraday.stub(:get) { fail Faraday::Error::ConnectionFailed, "boom" }
31
+ end
32
+ it "should raise helpful exception" do
33
+ klass = StackerBee::ConnectionError
34
+ expect(-> { subject }).to raise_error klass, /#{url}/
35
+ end
36
+ end
37
+
38
+ context "no protocol specified in URL" do
39
+ let(:url) { "wrong.com" }
40
+ it "should raise helpful exception" do
41
+ klass = StackerBee::ConnectionError
42
+ expect(-> { subject }).to raise_error klass, /no protocol/
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+
3
+ describe StackerBee::Middleware::Logger do
4
+ let(:app) { double }
5
+ let(:io) { StringIO.new }
6
+ let(:logger) { Logger.new(io) }
7
+ let(:log_string) { io.string }
8
+ let(:middleware) { described_class.new app, logger }
9
+ subject { middleware }
10
+
11
+ context "with a no logger specified" do
12
+ let(:middleware) { described_class.new app, nil }
13
+ its(:logger) { should be_a Logger }
14
+ end
15
+
16
+ describe "logging request" do
17
+ let(:env) do
18
+ {
19
+ method: "PATCH",
20
+ url: "http://localhost",
21
+ request_headers: { "User-Agent" => "RSpec" }
22
+ }
23
+ end
24
+ before do
25
+ middleware.log_request(env)
26
+ end
27
+ subject { log_string }
28
+ it { should include "PATCH" }
29
+ it { should include "localhost" }
30
+ it { should include "User-Agent" }
31
+ end
32
+
33
+ describe "logging a response" do
34
+ let(:status) { 200 }
35
+ let(:env) do
36
+ {
37
+ status: status,
38
+ body: "OK",
39
+ response_headers: { "Server" => "RSpec" }
40
+ }
41
+ end
42
+ before do
43
+ middleware.log_response(env)
44
+ end
45
+ subject { log_string }
46
+ it { should =~ /INFO.*#{status}/ }
47
+ it { should include "OK" }
48
+ it { should include "Server" }
49
+
50
+ context "failing status" do
51
+ let(:status) { 401 }
52
+ it { should =~ /ERROR.*#{status}/ }
53
+ end
54
+ end
55
+ end