fleet-api 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +48 -0
- data/LICENSE +201 -0
- data/README.md +2 -0
- data/Rakefile +8 -0
- data/fleet-api.gemspec +22 -0
- data/lib/fleet/client/job.rb +44 -0
- data/lib/fleet/client/state.rb +25 -0
- data/lib/fleet/client/unit.rb +32 -0
- data/lib/fleet/client.rb +116 -0
- data/lib/fleet/configuration.rb +44 -0
- data/lib/fleet/connection.rb +23 -0
- data/lib/fleet/error.rb +42 -0
- data/lib/fleet/request.rb +56 -0
- data/lib/fleet/service_definition.rb +63 -0
- data/lib/fleet/version.rb +3 -0
- data/lib/fleet.rb +15 -0
- data/lib/middleware/response/raise_error.rb +37 -0
- data/spec/fleet/client/job_spec.rb +123 -0
- data/spec/fleet/client/state_spec.rb +50 -0
- data/spec/fleet/client/unit_spec.rb +76 -0
- data/spec/fleet/client_spec.rb +247 -0
- data/spec/fleet/configuration_spec.rb +46 -0
- data/spec/fleet/connection_spec.rb +75 -0
- data/spec/fleet/error_spec.rb +29 -0
- data/spec/fleet/request_spec.rb +128 -0
- data/spec/fleet/service_definition_spec.rb +86 -0
- data/spec/fleet_spec.rb +87 -0
- data/spec/middleware/response/raise_error_spec.rb +90 -0
- data/spec/spec_helper.rb +9 -0
- metadata +171 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fleet/configuration'
|
3
|
+
|
4
|
+
describe Fleet::Configuration do
|
5
|
+
|
6
|
+
subject { Class.new { extend Fleet::Configuration } }
|
7
|
+
|
8
|
+
describe 'exposed attribes' do
|
9
|
+
Fleet::Configuration::VALID_OPTIONS_KEYS.each do |key|
|
10
|
+
it { should respond_to key.to_sym }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'default values' do
|
15
|
+
|
16
|
+
describe 'adapter' do
|
17
|
+
it 'is matches DEFAULT_ADAPTER' do
|
18
|
+
expect(subject.adapter).to eq Fleet::Configuration::DEFAULT_ADAPTER
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'fleet_api_url' do
|
23
|
+
it 'is matches DEFAULT_ETCD_API_URL' do
|
24
|
+
expect(subject.fleet_api_url).to eq Fleet::Configuration::DEFAULT_ETCD_API_URL
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'open_timeout' do
|
29
|
+
it 'is matches DEFAULT_OPEN_TIMEOUT' do
|
30
|
+
expect(subject.open_timeout).to eq Fleet::Configuration::DEFAULT_OPEN_TIMEOUT
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'read_timeout' do
|
35
|
+
it 'is matches DEFAULT_READ_TIMEOUT' do
|
36
|
+
expect(subject.read_timeout).to eq Fleet::Configuration::DEFAULT_READ_TIMEOUT
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'logger' do
|
41
|
+
it 'is matches DEFAULT_LOGGER' do
|
42
|
+
expect(subject.logger).to eq Fleet::Configuration::DEFAULT_LOGGER
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Fleet::Connection do
|
4
|
+
|
5
|
+
describe 'registered middleware' do
|
6
|
+
|
7
|
+
subject { Fleet::Client.new.connection }
|
8
|
+
|
9
|
+
handlers = [
|
10
|
+
Faraday::Request::UrlEncoded,
|
11
|
+
FaradayMiddleware::ParseJson,
|
12
|
+
Middleware::Response::RaiseError,
|
13
|
+
Faraday::Adapter::NetHttp
|
14
|
+
]
|
15
|
+
|
16
|
+
handlers.each do |handler|
|
17
|
+
it { expect(subject.builder.handlers).to include handler }
|
18
|
+
end
|
19
|
+
|
20
|
+
it "includes exactly #{handlers.count} handlers" do
|
21
|
+
expect(subject.builder.handlers.count).to eql handlers.count
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'connection options' do
|
26
|
+
|
27
|
+
let(:url) { 'http://foo.com/bar' }
|
28
|
+
let(:ssl_options) { { verify: true } }
|
29
|
+
let(:proxy) { 'http://proxy.com' }
|
30
|
+
|
31
|
+
subject do
|
32
|
+
Fleet::Client.new(
|
33
|
+
fleet_api_url: url,
|
34
|
+
ssl_options: ssl_options,
|
35
|
+
proxy: proxy).connection
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'scheme' do
|
39
|
+
it 'matches the scheme of the URL' do
|
40
|
+
expect(subject.scheme).to eq 'http'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'host' do
|
45
|
+
it 'matches the host of the URL' do
|
46
|
+
expect(subject.host).to eq 'foo.com'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'port' do
|
51
|
+
it 'matches the port of the URL' do
|
52
|
+
expect(subject.port).to eq 80
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'path_prefix' do
|
57
|
+
it 'matches the path of the URL' do
|
58
|
+
expect(subject.path_prefix).to eq '/bar'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'ssl' do
|
63
|
+
it 'matches the specified SSL options' do
|
64
|
+
expect(subject.ssl).to eq ssl_options
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'proxy' do
|
69
|
+
it 'matches the specified SSL options' do
|
70
|
+
expect(subject.proxy[:uri].to_s).to eq proxy
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Fleet::Error do
|
4
|
+
|
5
|
+
let(:message) { 'some message' }
|
6
|
+
let(:error_code) { 12345 }
|
7
|
+
let(:cause) { 'user error' }
|
8
|
+
|
9
|
+
subject { Fleet::Error.new(message, error_code, cause) }
|
10
|
+
|
11
|
+
it { should respond_to(:message) }
|
12
|
+
it { should respond_to(:error_code) }
|
13
|
+
it { should respond_to(:cause) }
|
14
|
+
|
15
|
+
describe '#initialize' do
|
16
|
+
|
17
|
+
it 'saves the passed-in message' do
|
18
|
+
expect(subject.message).to eq message
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'saves the passed-in error code' do
|
22
|
+
expect(subject.error_code).to eq error_code
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'saves the passed-in cause' do
|
26
|
+
expect(subject.cause).to eq cause
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Fleet::Request do
|
4
|
+
|
5
|
+
subject { Fleet::Client.new }
|
6
|
+
|
7
|
+
let(:path) { '/path' }
|
8
|
+
|
9
|
+
let(:request) do
|
10
|
+
double(:request,
|
11
|
+
options: {},
|
12
|
+
headers: {},
|
13
|
+
params: {},
|
14
|
+
'headers=' => nil,
|
15
|
+
'path=' => nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:response) do
|
19
|
+
double(:response, body: 'foo')
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:connection) { double(:connection) }
|
23
|
+
|
24
|
+
before do
|
25
|
+
allow(connection).to receive(:send).and_yield(request).and_return(response)
|
26
|
+
allow(subject).to receive(:connection).and_return(connection)
|
27
|
+
end
|
28
|
+
|
29
|
+
[:get, :delete, :head, :put, :post].each do |method|
|
30
|
+
|
31
|
+
context "##{method}" do
|
32
|
+
|
33
|
+
it 'sets the path' do
|
34
|
+
expect(request).to receive(:path=).with(path)
|
35
|
+
subject.send(method, path)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'sets the headers' do
|
39
|
+
headers = { foo: :bar }
|
40
|
+
expect(request).to receive(:headers=).with(hash_including(headers))
|
41
|
+
subject.send(method, path, {}, headers)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns the response body' do
|
45
|
+
expect(subject.send(method, path)).to eql(response.body)
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when a Faraday::Error::ConnectionFailed error is raised' do
|
49
|
+
|
50
|
+
before do
|
51
|
+
allow(connection).to receive(:send)
|
52
|
+
.and_raise(Faraday::Error::ConnectionFailed, 'oops')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'raises a Fleet::ConnectionError' do
|
56
|
+
expect { subject.send(method, path) }.to raise_error(Fleet::ConnectionError, 'oops')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
[:get, :delete, :head].each do |method|
|
63
|
+
|
64
|
+
context "##{method}" do
|
65
|
+
|
66
|
+
context 'when options provided' do
|
67
|
+
|
68
|
+
it 'sets options on the request' do
|
69
|
+
options = { a: :b }
|
70
|
+
expect(request).to receive(:params=).with(options)
|
71
|
+
subject.send(method, path, options)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when no options provided' do
|
76
|
+
|
77
|
+
it 'does not set options on the request' do
|
78
|
+
expect(request).to_not receive(:params=)
|
79
|
+
subject.send(method, path)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
[:put, :post].each do |method|
|
86
|
+
|
87
|
+
context "##{method}" do
|
88
|
+
|
89
|
+
context 'when options provided' do
|
90
|
+
|
91
|
+
it 'sets options on the request' do
|
92
|
+
options = { a: :b }
|
93
|
+
expect(request).to receive(:body=).with(options)
|
94
|
+
subject.send(method, path, options)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when no options provided' do
|
99
|
+
|
100
|
+
it 'does not set options on the request' do
|
101
|
+
expect(request).to_not receive(:body=)
|
102
|
+
subject.send(method, path)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when querystring AND body provided' do
|
107
|
+
let(:options) { { querystring: { a: :b }, body: { c: :d } } }
|
108
|
+
|
109
|
+
before do
|
110
|
+
allow(request).to receive(:params=)
|
111
|
+
allow(request).to receive(:body=)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'sets the querystring as request params' do
|
115
|
+
expect(request).to receive(:params=).with(options[:querystring])
|
116
|
+
subject.send(method, path, options)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'sets the body as request body' do
|
120
|
+
expect(request).to receive(:body=).with(options[:body])
|
121
|
+
subject.send(method, path, options)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Fleet::ServiceDefinition do
|
4
|
+
|
5
|
+
let(:name) { 'myservice.service' }
|
6
|
+
|
7
|
+
let(:service_hash) do
|
8
|
+
{
|
9
|
+
'Unit' => {
|
10
|
+
'Description' => 'infinite loop'
|
11
|
+
},
|
12
|
+
'Service' => {
|
13
|
+
'ExecStart' => "/bin/bash -c \"while true; do sleep 1; done\""
|
14
|
+
}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
subject { described_class.new(name) }
|
19
|
+
|
20
|
+
it { should respond_to :name }
|
21
|
+
|
22
|
+
describe '#initialize' do
|
23
|
+
|
24
|
+
subject { described_class.new(name) }
|
25
|
+
|
26
|
+
describe 'name' do
|
27
|
+
it 'should equal the passed-in name' do
|
28
|
+
expect(subject.name).to eq name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#to_unit' do
|
34
|
+
|
35
|
+
subject { described_class.new(name, service_hash) }
|
36
|
+
|
37
|
+
it 'provides a fleet formatted unit definition' do
|
38
|
+
|
39
|
+
raw = <<UNIT_FILE
|
40
|
+
[Unit]
|
41
|
+
Description=#{service_hash['Unit']['Description']}
|
42
|
+
|
43
|
+
[Service]
|
44
|
+
ExecStart=#{service_hash['Service']['ExecStart']}
|
45
|
+
UNIT_FILE
|
46
|
+
|
47
|
+
expected = {
|
48
|
+
'Contents' => {
|
49
|
+
'Unit' => {
|
50
|
+
'Description' => [service_hash['Unit']['Description']]
|
51
|
+
},
|
52
|
+
'Service' => {
|
53
|
+
'ExecStart' => [service_hash['Service']['ExecStart']]
|
54
|
+
}
|
55
|
+
},
|
56
|
+
'Raw' => raw
|
57
|
+
}
|
58
|
+
|
59
|
+
expect(subject.to_unit).to eq expected
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#to_job' do
|
64
|
+
|
65
|
+
subject { described_class.new(name, service_hash) }
|
66
|
+
|
67
|
+
it 'generates the appropriate job definition' do
|
68
|
+
|
69
|
+
expected = {
|
70
|
+
'Name' => name,
|
71
|
+
'UnitHash' => [111, 150, 87, 109, 217, 26, 190, 221, 31, 28, 8, 211, 198, 126, 76, 157, 106, 164, 220, 134]
|
72
|
+
}
|
73
|
+
|
74
|
+
expect(subject.to_job).to eq expected
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#sha1' do
|
79
|
+
|
80
|
+
subject { described_class.new(name, service_hash) }
|
81
|
+
|
82
|
+
it 'generates the appropriate sha1 hash' do
|
83
|
+
expect(subject.sha1).to eq '6f96576dd91abedd1f1c08d3c67e4c9d6aa4dc86'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/spec/fleet_spec.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Fleet do
|
4
|
+
|
5
|
+
after do
|
6
|
+
Fleet.reset
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '.new' do
|
10
|
+
|
11
|
+
it 'returns a Fleet::Client' do
|
12
|
+
expect(Fleet.new).to be_a Fleet::Client
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when no options specified' do
|
16
|
+
|
17
|
+
Fleet::Configuration::VALID_OPTIONS_KEYS.each do |option|
|
18
|
+
|
19
|
+
it "new Fleet::Client inherits :#{option} default from Fleet" do
|
20
|
+
expect(Fleet.new.send(option)).to eq Fleet.send(option)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when options are specified' do
|
26
|
+
|
27
|
+
Fleet::Configuration::VALID_OPTIONS_KEYS.each do |option|
|
28
|
+
|
29
|
+
it "new Fleet::Client receives specified :#{option} value" do
|
30
|
+
expect(Fleet.new({option => 'foo'}).send(option)).to eq 'foo'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '.fleet_api_url' do
|
37
|
+
|
38
|
+
let(:url) { 'http://foo.com/bar' }
|
39
|
+
|
40
|
+
before do
|
41
|
+
stub_const('Fleet::Configuration::DEFAULT_ETCD_API_URL', url)
|
42
|
+
Fleet.reset
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'defaults to the value of DEFAULT_ETCD_API_URL' do
|
46
|
+
expect(Fleet.fleet_api_url).to eq url
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '.open_timeout' do
|
51
|
+
it 'defaults to 2' do
|
52
|
+
expect(Fleet.open_timeout).to eq 2
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '.read_timeout' do
|
57
|
+
it 'defaults to 5' do
|
58
|
+
expect(Fleet.read_timeout).to eq 5
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '.ssl_options' do
|
63
|
+
it 'defaults to { verify: false }' do
|
64
|
+
expect(Fleet.ssl_options).to eq(verify: false)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '.proxy' do
|
69
|
+
it 'defaults to nil' do
|
70
|
+
expect(Fleet.proxy).to be_nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '.configure' do
|
75
|
+
it "accepts a block" do
|
76
|
+
expect { Fleet.configure {} }.to_not raise_error
|
77
|
+
end
|
78
|
+
|
79
|
+
it "yields self" do
|
80
|
+
Fleet.configure { |conf| expect(conf).to be(Fleet) }
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns true" do
|
84
|
+
expect(Fleet.configure {}).to eq true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Middleware::Response::RaiseError do
|
4
|
+
|
5
|
+
describe '#on_complete' do
|
6
|
+
|
7
|
+
context 'when HTTP status is 200' do
|
8
|
+
|
9
|
+
let(:env) { { status: 200 } }
|
10
|
+
|
11
|
+
it 'raises no errors' do
|
12
|
+
expect { subject.on_complete(env) }.to_not raise_error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when the HTTP status is a known error (404)' do
|
17
|
+
|
18
|
+
let(:message) { 'not found' }
|
19
|
+
let(:error_code) { 999 }
|
20
|
+
let(:cause) { 'just because ' }
|
21
|
+
|
22
|
+
let(:env) do
|
23
|
+
{
|
24
|
+
status: 404,
|
25
|
+
body: "{ \"message\": \"#{message}\", \"errorCode\": #{error_code}, \"cause\": \"#{cause}\" }"
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raises a NotFound execption' do
|
30
|
+
expect { subject.on_complete(env) }.to raise_error(Fleet::NotFound)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'sets the message on the exception' do
|
34
|
+
begin
|
35
|
+
subject.on_complete(env)
|
36
|
+
rescue Fleet::NotFound => ex
|
37
|
+
expect(ex.message).to eq message
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'sets the error code on the exception' do
|
42
|
+
begin
|
43
|
+
subject.on_complete(env)
|
44
|
+
rescue Fleet::NotFound => ex
|
45
|
+
expect(ex.error_code).to eq error_code
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'sets the cause on the exception' do
|
50
|
+
begin
|
51
|
+
subject.on_complete(env)
|
52
|
+
rescue Fleet::NotFound => ex
|
53
|
+
expect(ex.cause).to eq cause
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'when HTTP status is an unknown error' do
|
59
|
+
|
60
|
+
let(:env) do
|
61
|
+
{
|
62
|
+
status: 499,
|
63
|
+
body: "{ \"message\": \"err\" }"
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'raises an Error execption' do
|
68
|
+
expect { subject.on_complete(env) }.to raise_error(Fleet::Error)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when error body is not JSON parseable' do
|
73
|
+
|
74
|
+
let(:env) do
|
75
|
+
{
|
76
|
+
status: 499,
|
77
|
+
body: 'FOO BAR'
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'sets the error message to be the response body' do
|
82
|
+
begin
|
83
|
+
subject.on_complete(env)
|
84
|
+
rescue Fleet::Error => ex
|
85
|
+
expect(ex.message).to eq env[:body]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|