kumo_dockercloud 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/lib/kumo_dockercloud.rb +1 -0
- data/lib/kumo_dockercloud/docker_cloud_api.rb +9 -1
- data/lib/kumo_dockercloud/environment_config.rb +5 -0
- data/lib/kumo_dockercloud/service.rb +16 -27
- data/lib/kumo_dockercloud/service_checker.rb +35 -0
- data/lib/kumo_dockercloud/stack.rb +13 -11
- data/lib/kumo_dockercloud/version.rb +1 -1
- data/spec/kumo_dockercloud/docker_cloud_api_spec.rb +46 -1
- data/spec/kumo_dockercloud/environment_config_spec.rb +81 -61
- data/spec/kumo_dockercloud/environment_spec.rb +0 -3
- data/spec/kumo_dockercloud/service_checker_spec.rb +67 -0
- data/spec/kumo_dockercloud/service_spec.rb +51 -84
- data/spec/kumo_dockercloud/stack_file_spec.rb +0 -3
- data/spec/kumo_dockercloud/stack_spec.rb +79 -80
- data/spec/kumo_dockercloud/state_validator_spec.rb +0 -3
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2582ba0407f7bdd51b0a313b93e9508719ce48c0
|
4
|
+
data.tar.gz: 9d1e586ca6c319a3b9db096d571b772d8c026d48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c67e36f20cd1691b05d0fad87f9133a38f33d3a4a9efda200a213e318b8fe2b13a4474c54df70b5cbdcbaa4e59a103ec19f8103e8d7082dd0bf6ea8efb669e48
|
7
|
+
data.tar.gz: 5109665c78f88ff6af562c82d0030d37696068737474e531f2848a970b0d5f62ba59397e00dc2ac21dd81c231b6f8865067f96e00998e1eba04b36a9b616ca12
|
data/.rspec
CHANGED
data/lib/kumo_dockercloud.rb
CHANGED
@@ -4,7 +4,7 @@ require 'base64' # left out of DockerCloud gem
|
|
4
4
|
module KumoDockerCloud
|
5
5
|
class DockerCloudApi
|
6
6
|
extend Forwardable
|
7
|
-
|
7
|
+
def_delegators :@client, :services, :stacks
|
8
8
|
|
9
9
|
def initialize(options = {})
|
10
10
|
options[:username] ||= ENV['DOCKERCLOUD_USER']
|
@@ -33,5 +33,13 @@ module KumoDockerCloud
|
|
33
33
|
service.containers
|
34
34
|
end.flatten
|
35
35
|
end
|
36
|
+
|
37
|
+
def service_by_resource_uri(resource_uri)
|
38
|
+
@client.services.get_from_uri(resource_uri)
|
39
|
+
end
|
40
|
+
|
41
|
+
def stack_by_resource_uri(resource_uri)
|
42
|
+
@client.stacks.get_from_uri(resource_uri)
|
43
|
+
end
|
36
44
|
end
|
37
45
|
end
|
@@ -47,6 +47,11 @@ module KumoDockerCloud
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
+
def tagged_app_image(service_name)
|
51
|
+
service = docker_cloud_api.service_by_stack_and_service_name(stack_name, service_name)
|
52
|
+
service ? service.image_name : "redbubble/#{app_name}:master"
|
53
|
+
end
|
54
|
+
|
50
55
|
def image_tag
|
51
56
|
image_name.split(':').last
|
52
57
|
end
|
@@ -9,42 +9,31 @@ module KumoDockerCloud
|
|
9
9
|
@name = service_name
|
10
10
|
end
|
11
11
|
|
12
|
+
def self.service_by_resource_uri(resource_uri)
|
13
|
+
api = KumoDockerCloud::DockerCloudApi.new
|
14
|
+
service = api.service_by_resource_uri(resource_uri)
|
15
|
+
stack = api.stacks.get_from_uri(service.info[:stack])
|
16
|
+
|
17
|
+
self.new(stack.name, service.name)
|
18
|
+
end
|
19
|
+
|
12
20
|
def deploy(version)
|
13
21
|
update_image(version)
|
14
22
|
redeploy
|
15
23
|
end
|
16
24
|
|
17
|
-
def
|
18
|
-
|
19
|
-
all_tests_passed = true
|
20
|
-
containers.each do |container|
|
21
|
-
checks.each do |check|
|
22
|
-
unless check.call(container)
|
23
|
-
all_tests_passed = false
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
unless all_tests_passed
|
29
|
-
print '.'
|
30
|
-
sleep(5)
|
31
|
-
check(checks, timeout)
|
32
|
-
end
|
33
|
-
|
34
|
-
true
|
35
|
-
end
|
36
|
-
rescue
|
37
|
-
raise KumoDockerCloud::ServiceDeployError.new("One or more checks failed to pass within the timeout")
|
25
|
+
def linked_services
|
26
|
+
get_service.linked_to_service.map { |link| KumoDockerCloud::Service.service_by_resource_uri(link[:to_service]) }
|
38
27
|
end
|
39
28
|
|
40
29
|
def links
|
41
|
-
get_service.linked_to_service
|
30
|
+
get_service.linked_to_service
|
42
31
|
end
|
43
32
|
|
44
|
-
def set_link(service_to_link)
|
33
|
+
def set_link(service_to_link, link_internal_name)
|
45
34
|
linked_service = {
|
46
35
|
to_service: service_to_link.resource_uri,
|
47
|
-
name:
|
36
|
+
name: link_internal_name,
|
48
37
|
from_service: resource_uri
|
49
38
|
}
|
50
39
|
|
@@ -59,13 +48,13 @@ module KumoDockerCloud
|
|
59
48
|
get_service.resource_uri
|
60
49
|
end
|
61
50
|
|
62
|
-
private
|
63
|
-
attr_reader :stack_name
|
64
|
-
|
65
51
|
def containers
|
66
52
|
get_service.containers
|
67
53
|
end
|
68
54
|
|
55
|
+
private
|
56
|
+
attr_reader :stack_name
|
57
|
+
|
69
58
|
def update_image(version)
|
70
59
|
docker_cloud_api.services.update(uuid, image: "#{image_name}:#{version}")
|
71
60
|
rescue RestClient::InternalServerError
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module KumoDockerCloud
|
2
|
+
class ServiceChecker
|
3
|
+
attr_reader :checks, :timeout, :quiet_time
|
4
|
+
|
5
|
+
def initialize(checks = [], timeout = 300, quiet_time = 5)
|
6
|
+
@checks = checks
|
7
|
+
@timeout = timeout
|
8
|
+
@quiet_time = quiet_time
|
9
|
+
end
|
10
|
+
|
11
|
+
def verify(service)
|
12
|
+
Timeout::timeout(timeout) do
|
13
|
+
|
14
|
+
while any_check_failing?(service)
|
15
|
+
print '.'
|
16
|
+
sleep(quiet_time)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
rescue Timeout::Error
|
21
|
+
raise KumoDockerCloud::ServiceDeployError.new("One or more checks failed to pass within the timeout")
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def any_check_failing?(service)
|
27
|
+
checks.each do |check|
|
28
|
+
service.containers.each do |container|
|
29
|
+
return true unless check.call(container)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -9,35 +9,37 @@ module KumoDockerCloud
|
|
9
9
|
@options = options
|
10
10
|
end
|
11
11
|
|
12
|
-
def deploy(service_name, version,
|
12
|
+
def deploy(service_name, version, checker = ServiceChecker.new)
|
13
13
|
validate_params(service_name, 'Service name')
|
14
14
|
validate_params(version, 'Version')
|
15
15
|
|
16
16
|
service = Service.new(stack_name, service_name)
|
17
17
|
service.deploy(version)
|
18
|
-
|
18
|
+
checker.verify(service)
|
19
19
|
end
|
20
20
|
|
21
21
|
def deploy_blue_green(options)
|
22
22
|
service_names = options[:service_names]
|
23
23
|
version = options[:version]
|
24
|
-
|
25
|
-
check_timeout = options[:check_timeout]
|
24
|
+
checker = options[:checker] || ServiceChecker.new
|
26
25
|
switching_service_name = options[:switching_service_name]
|
27
26
|
|
28
27
|
validate_params(version, "Version")
|
29
28
|
validate_params(service_names, "Service names")
|
30
29
|
validate_params(switching_service_name, "Switching service name")
|
31
30
|
|
32
|
-
services = service_names.map { |service_name| Service.new(stack_name, service_name) }
|
33
|
-
|
34
31
|
switching_service = Service.new(stack_name, switching_service_name)
|
35
|
-
|
36
|
-
|
32
|
+
link = switching_service.links.find { |link| service_names.include?(Service.service_by_resource_uri(link[:to_service]).name) }
|
33
|
+
active_service = Service.service_by_resource_uri(link[:to_service])
|
34
|
+
|
35
|
+
inactive_service_name = service_names.find { |name| name != active_service.name }
|
36
|
+
inactive_service = Service.new(stack_name, inactive_service_name)
|
37
|
+
|
38
|
+
inactive_service.deploy(version)
|
39
|
+
checker.verify(inactive_service)
|
37
40
|
|
38
|
-
|
39
|
-
|
40
|
-
green_service.stop
|
41
|
+
switching_service.set_link(inactive_service, link[:name])
|
42
|
+
active_service.stop
|
41
43
|
end
|
42
44
|
|
43
45
|
private
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'kumo_dockercloud/docker_cloud_api'
|
2
1
|
require 'webmock/rspec'
|
3
2
|
|
4
3
|
describe KumoDockerCloud::DockerCloudApi do
|
@@ -160,6 +159,52 @@ describe KumoDockerCloud::DockerCloudApi do
|
|
160
159
|
end
|
161
160
|
end
|
162
161
|
|
162
|
+
context '#service_by_resource_uri' do
|
163
|
+
let(:resource_uri) { "look_at_me_im_a_resource_id" }
|
164
|
+
let(:service_name) { 'batman' }
|
165
|
+
let(:service) { double(DockerCloud::Service, name: service_name) }
|
166
|
+
let(:services_mock) { double(DockerCloud::StackAPI, get_from_uri: service ) }
|
167
|
+
|
168
|
+
subject { api.service_by_resource_uri(resource_uri) }
|
169
|
+
|
170
|
+
before do
|
171
|
+
allow_any_instance_of(::DockerCloud::Client).to receive(:services).and_return(services_mock)
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'when the service exists' do
|
175
|
+
it { should == service }
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'when there is no service' do
|
179
|
+
let(:services_mock) { double(DockerCloud::ServiceAPI, get_from_uri: nil ) }
|
180
|
+
|
181
|
+
it { should be_nil }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context '#stack_by_resource_uri' do
|
186
|
+
let(:resource_uri) { "look_at_me_im_a_resource_id" }
|
187
|
+
let(:stack_name) { 'batman' }
|
188
|
+
let(:stack) { double(DockerCloud::Stack, name: stack_name) }
|
189
|
+
let(:stacks_mock) { double(DockerCloud::StackAPI, get_from_uri: stack ) }
|
190
|
+
|
191
|
+
subject { api.stack_by_resource_uri(resource_uri) }
|
192
|
+
|
193
|
+
before do
|
194
|
+
allow_any_instance_of(::DockerCloud::Client).to receive(:stacks).and_return(stacks_mock)
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'when the stack exists' do
|
198
|
+
it { should == stack }
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'when there is no service' do
|
202
|
+
let(:stacks_mock) { double(DockerCloud::StackAPI, get_from_uri: nil ) }
|
203
|
+
|
204
|
+
it { should be_nil }
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
163
208
|
context 'forwarded methods' do
|
164
209
|
describe '#services' do
|
165
210
|
let(:client) { instance_double(DockerCloud::Client) }
|
@@ -1,12 +1,11 @@
|
|
1
|
-
require 'kumo_dockercloud/environment_config'
|
2
|
-
require 'kumo_dockercloud/docker_cloud_api'
|
3
|
-
|
4
1
|
describe KumoDockerCloud::EnvironmentConfig do
|
5
2
|
let(:env_name) { 'test' }
|
3
|
+
let(:app_name) { "app-foo" }
|
6
4
|
let(:config_path) { File.join(__dir__, '../fixtures/config') }
|
7
|
-
subject(:instance) { described_class.new(app_name:
|
5
|
+
subject(:instance) { described_class.new(app_name: app_name, env_name: env_name, config_path: config_path) }
|
8
6
|
|
9
7
|
let(:docker_cloud_api) { instance_double('KumoDockerCloud::DockerCloudApi') }
|
8
|
+
before { allow(KumoDockerCloud::DockerCloudApi).to receive(:new).and_return docker_cloud_api }
|
10
9
|
|
11
10
|
describe '#get_binding' do
|
12
11
|
let(:services_for_stack) { [] }
|
@@ -22,87 +21,108 @@ describe KumoDockerCloud::EnvironmentConfig do
|
|
22
21
|
expect(subject).to eq env_name
|
23
22
|
end
|
24
23
|
end
|
24
|
+
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
let(:secrets_data) do
|
29
|
-
{
|
30
|
-
'testkey' => 'someval',
|
31
|
-
'moretest' => 'otherval'
|
32
|
-
}
|
33
|
-
end
|
26
|
+
describe '#plain_text_secrets' do
|
27
|
+
subject { instance.plain_text_secrets }
|
34
28
|
|
35
|
-
|
29
|
+
context 'with no encrypted secrets' do
|
30
|
+
let(:secrets_data) do
|
31
|
+
{
|
32
|
+
'testkey' => 'someval',
|
33
|
+
'moretest' => 'otherval'
|
34
|
+
}
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
37
|
+
it do
|
38
|
+
expect(subject).to eq secrets_data
|
40
39
|
end
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
context 'with some encrypted secrets' do
|
43
|
+
let(:env_name) { 'test_encrypted' }
|
44
|
+
let(:plain_value) { 'otherval' }
|
45
45
|
|
46
|
-
|
46
|
+
let(:kms) { double('KumoKi::KMS') }
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
let(:plain_data) do
|
49
|
+
{
|
50
|
+
'testkey' => 'someval',
|
51
|
+
'enctest' => plain_value
|
52
|
+
}
|
53
|
+
end
|
54
54
|
|
55
|
-
|
55
|
+
before do
|
56
|
+
allow(KumoKi::KMS).to receive(:new).and_return(kms)
|
57
|
+
allow(kms).to receive(:decrypt).with('ZW5jcnlwdGVkIGJpbmFyeSBkYXRh').and_return(plain_value)
|
58
|
+
end
|
56
59
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
it do
|
61
|
+
expect(subject).to eq plain_data
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
61
65
|
|
62
|
-
|
63
|
-
|
64
|
-
|
66
|
+
describe "#tagged_app_image" do
|
67
|
+
subject { instance.tagged_app_image(service_name) }
|
68
|
+
|
69
|
+
let(:service_name) { "app-foo-service-a" }
|
70
|
+
|
71
|
+
before { expect(docker_cloud_api).to receive(:service_by_stack_and_service_name).with(instance.stack_name, service_name).and_return(service) }
|
72
|
+
|
73
|
+
context "no service present in stack" do
|
74
|
+
let(:service) { nil }
|
75
|
+
|
76
|
+
it "returns the master application image" do
|
77
|
+
expect(subject).to eq("redbubble/#{app_name}:master")
|
65
78
|
end
|
66
79
|
end
|
67
80
|
|
68
|
-
context
|
69
|
-
|
70
|
-
|
71
|
-
|
81
|
+
context "service present in stack" do
|
82
|
+
let(:service) { instance_double(DockerCloud::Service, image_name: current_image_name) }
|
83
|
+
let(:current_image_name) { "redbubble/#{app_name}:14" }
|
84
|
+
|
85
|
+
it "returns the current service image" do
|
86
|
+
expect(subject).to eq(current_image_name)
|
72
87
|
end
|
88
|
+
end
|
73
89
|
|
74
|
-
|
75
|
-
let(:string) { 'image_name' }
|
90
|
+
end
|
76
91
|
|
77
|
-
|
78
|
-
|
79
|
-
let(:services_for_stack) { [ double(DockerCloud::Service, image_name: image_name) ] }
|
92
|
+
context 'with API mocked' do
|
93
|
+
before { allow(docker_cloud_api).to receive(:services_by_stack_name).with("#{app_name}-#{env_name}").and_return(services_for_stack) }
|
80
94
|
|
81
|
-
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|
95
|
+
describe '#image_name' do
|
96
|
+
subject { instance.image_name }
|
85
97
|
|
86
|
-
|
87
|
-
|
98
|
+
context 'when there is a service for the given stack' do
|
99
|
+
let(:image_name) { 'redbubble/application-stack-name:1234' }
|
100
|
+
let(:services_for_stack) { [ double(DockerCloud::Service, image_name: image_name) ] }
|
88
101
|
|
89
|
-
|
102
|
+
it 'uses the pre-exisiting image name' do
|
103
|
+
expect(subject).to eq image_name
|
90
104
|
end
|
91
105
|
end
|
92
106
|
|
93
|
-
|
94
|
-
let(:services_for_stack) { [
|
107
|
+
context 'when there is no service for the given stack' do
|
108
|
+
let(:services_for_stack) { [] }
|
95
109
|
|
96
|
-
|
110
|
+
it { expect(subject).to eq "redbubble/#{app_name}:master" }
|
111
|
+
end
|
112
|
+
end
|
97
113
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
114
|
+
describe '#image_tag' do
|
115
|
+
let(:services_for_stack) { [ double(DockerCloud::Service, image_name: image_name)] }
|
116
|
+
|
117
|
+
subject { instance.image_tag }
|
118
|
+
|
119
|
+
context "when the image name is 'redbubble/assete-wala:latest'" do
|
120
|
+
let(:image_name) { 'redbubble/application-stack-name:latest' }
|
121
|
+
it { expect(subject).to eq 'latest' }
|
122
|
+
end
|
123
|
+
context "when the image name is 'some-registry/mything:9999'" do
|
124
|
+
let(:image_name) { 'some-registry/mything:9999' }
|
125
|
+
it { expect(subject).to eq '9999' }
|
106
126
|
end
|
107
127
|
end
|
108
128
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
describe KumoDockerCloud::ServiceChecker do
|
2
|
+
describe ".initialize" do
|
3
|
+
context "defaults" do
|
4
|
+
subject { described_class.new }
|
5
|
+
|
6
|
+
it "has no checks" do
|
7
|
+
expect(subject.checks).to be_empty
|
8
|
+
end
|
9
|
+
|
10
|
+
it "has a timeout of 300 seconds" do
|
11
|
+
expect(subject.timeout).to eq(300)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has a quiet_time of 5 seconds" do
|
15
|
+
expect(subject.timeout).to eq(300)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#verify' do
|
21
|
+
let(:happy_check) { lambda { |container| expect(container).to eq(container); true } }
|
22
|
+
let(:sad_check) { lambda { |container| expect(container).to eq(container); false } }
|
23
|
+
let(:container) { double(:my_container) }
|
24
|
+
let(:checks) {[happy_check]}
|
25
|
+
let(:timeout) { 5 }
|
26
|
+
|
27
|
+
let(:containers) { [container, container] }
|
28
|
+
|
29
|
+
let(:service) { instance_double(KumoDockerCloud::Service, containers: containers) }
|
30
|
+
|
31
|
+
subject { described_class.new(checks, timeout, 1).verify(service) }
|
32
|
+
|
33
|
+
context "all checks successful" do
|
34
|
+
it "runs without incident" do
|
35
|
+
subject
|
36
|
+
end
|
37
|
+
|
38
|
+
context "no checks" do
|
39
|
+
let(:checks) { [] }
|
40
|
+
|
41
|
+
it "runs without retrieving containers" do
|
42
|
+
expect(service).not_to receive(:containers)
|
43
|
+
subject
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "timing out check" do
|
49
|
+
let(:timeout) { 2 }
|
50
|
+
let(:checks) { [sad_check] }
|
51
|
+
|
52
|
+
it "raises an error" do
|
53
|
+
expect { subject }.to raise_error(KumoDockerCloud::ServiceDeployError, "One or more checks failed to pass within the timeout")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "second time is the charm" do
|
58
|
+
let(:mutating_state) { [] }
|
59
|
+
let(:mutating_check) { lambda { |container| mutating_state << 1; mutating_state.size > 1 } }
|
60
|
+
let(:checks) { [mutating_check] }
|
61
|
+
|
62
|
+
it "runs without incident" do
|
63
|
+
subject
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,12 +1,10 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'httpi'
|
3
|
-
|
4
1
|
describe KumoDockerCloud::Service do
|
5
2
|
let(:service_image) { "repository/docker_image_name:version" }
|
6
3
|
let(:service_uuid) { "i_am_a_unique_snowflower" }
|
7
4
|
let(:docker_cloud_service) { double(:service, uuid: service_uuid, image_name: service_image, containers: [], resource_uri: "api/v1/#{service_uuid}")}
|
8
5
|
let(:docker_cloud_services_api) { double(:services_api) }
|
9
|
-
let(:
|
6
|
+
let(:docker_cloud_stacks_api) { instance_double(DockerCloud::StackAPI, :stack_api) }
|
7
|
+
let(:docker_cloud_api) { instance_double(KumoDockerCloud::DockerCloudApi, services: docker_cloud_services_api, stacks: docker_cloud_stacks_api)}
|
10
8
|
|
11
9
|
subject { described_class.new('stack_name', 'service_name') }
|
12
10
|
|
@@ -35,100 +33,69 @@ describe KumoDockerCloud::Service do
|
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
38
|
-
describe
|
39
|
-
let(:
|
40
|
-
let(:
|
41
|
-
let(:
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
36
|
+
describe "#linked_services" do
|
37
|
+
let(:linked_service_uuid) { "i_am_the_db" }
|
38
|
+
let(:linked_service_resource_uri) { "/api/app/v1/service/#{linked_service_uuid}" }
|
39
|
+
let(:linked_service_internal_name) { "db" }
|
40
|
+
let(:linked_service_name) { "db-1" }
|
41
|
+
let(:stack_resource_uri) { "stack_resource_uri" }
|
42
|
+
let(:linked_service) { double(:linked_service, uuid: linked_service_uuid, resource_uri: linked_service_resource_uri, info: { stack: stack_resource_uri}, name: linked_service_name) }
|
43
|
+
let(:linked_to_service) do
|
44
|
+
{
|
45
|
+
from_service: service_uuid,
|
46
|
+
name: linked_service_internal_name,
|
47
|
+
to_service: linked_service_resource_uri
|
48
|
+
}
|
47
49
|
end
|
48
|
-
let(:
|
49
|
-
let(:check_timeout) { 300 }
|
50
|
-
let(:whale1) { double(:whale1, container_ports: [{endpoint_uri: "http://whale1.test"}], reload: nil) }
|
51
|
-
let(:whale2) { double(:whale2, state: "Running", container_ports: [{endpoint_uri: "http://whale2.test"}], reload: nil)}
|
52
|
-
let(:containers) { [whale1, whale2] }
|
50
|
+
let(:stack) { double(:docker_cloud_stack, name: "my-stack", resource_uri: stack_resource_uri)}
|
53
51
|
|
54
52
|
before do
|
55
|
-
allow(
|
56
|
-
allow(http_lib).to receive(:get).with("http://whale2.test/site_status").and_return("timeout", "timeout", 200)
|
57
|
-
allow(whale1).to receive(:state).and_return("Starting", "Running")
|
58
|
-
allow(docker_cloud_service).to receive(:containers).and_return(containers)
|
53
|
+
allow(docker_cloud_service).to receive(:linked_to_service).and_return([linked_to_service])
|
59
54
|
end
|
60
55
|
|
61
|
-
it
|
62
|
-
allow(
|
63
|
-
|
64
|
-
|
56
|
+
it "returns a list of KumoDockerCloud::Service object that are linked to from this service" do
|
57
|
+
allow(docker_cloud_api).to receive(:service_by_resource_uri).with(linked_service_resource_uri).and_return(linked_service)
|
58
|
+
allow(docker_cloud_stacks_api).to receive(:get_from_uri).with(stack_resource_uri).and_return(stack)
|
59
|
+
allow(docker_cloud_api).to receive(:service_by_stack_and_service_name).with(stack.name, linked_service_name).and_return(linked_service)
|
65
60
|
|
61
|
+
links = subject.linked_services
|
62
|
+
expect(links.first).to have_attributes(resource_uri: linked_service_resource_uri)
|
63
|
+
expect(links.size).to eq(1)
|
64
|
+
end
|
66
65
|
|
67
|
-
it
|
68
|
-
|
69
|
-
|
70
|
-
expect { subject.check(checks, short_timeout) }.to raise_error(KumoDockerCloud::ServiceDeployError, "One or more checks failed to pass within the timeout")
|
66
|
+
it "returns an empty array if there are no links" do
|
67
|
+
allow(docker_cloud_service).to receive(:linked_to_service).and_return([])
|
68
|
+
expect(subject.linked_services).to eq([])
|
71
69
|
end
|
70
|
+
end
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
before do
|
85
|
-
allow(docker_cloud_service).to receive(:linked_to_service).and_return([linked_to_service])
|
86
|
-
end
|
87
|
-
|
88
|
-
it "returns a list of KumoDockerCloud::Service object that are linked to from this service" do
|
89
|
-
links = subject.links
|
90
|
-
expect(links.first).to have_attributes(name: linked_service_name)
|
91
|
-
expect(links.size).to eq(1)
|
92
|
-
end
|
93
|
-
|
94
|
-
it "returns an empty array if there are no links" do
|
95
|
-
allow(docker_cloud_service).to receive(:linked_to_service).and_return([])
|
96
|
-
expect(subject.links).to eq([])
|
97
|
-
end
|
72
|
+
describe "#set_link" do
|
73
|
+
let(:linked_service_uuid) { "i_am_the_db" }
|
74
|
+
let(:linked_service_name) { "db-1" }
|
75
|
+
|
76
|
+
let(:linked_service_internal_name) { "db" }
|
77
|
+
let(:linked_to_service) do
|
78
|
+
{
|
79
|
+
to_service: "api/v1/#{linked_service_uuid}",
|
80
|
+
name: linked_service_internal_name,
|
81
|
+
from_service: "api/v1/#{service_uuid}"
|
82
|
+
}
|
98
83
|
end
|
99
84
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
name: linked_service_name,
|
107
|
-
from_service: "api/v1/#{service_uuid}"
|
108
|
-
}
|
109
|
-
end
|
110
|
-
let(:linked_service) { KumoDockerCloud::Service.new('stack_name', linked_service_name) }
|
111
|
-
let(:this_service) { double(:this_service, uuid: service_uuid, resource_uri: "api/v1/#{service_uuid}") }
|
112
|
-
let(:linked_service) { double(:linked_service, uuid: linked_service_uuid, resource_uri: "api/v1/#{linked_service_uuid}", name: linked_service_name) }
|
113
|
-
|
114
|
-
before do
|
115
|
-
allow(docker_cloud_api).to receive(:service_by_stack_and_service_name).with('stack_name', 'service_name').and_return(this_service)
|
116
|
-
allow(docker_cloud_api).to receive(:service_by_stack_and_service_name).with('stack_name', linked_service_name).and_return(linked_service)
|
117
|
-
end
|
118
|
-
|
119
|
-
it "updates the link attribute" do
|
120
|
-
expect(docker_cloud_services_api).to receive(:update).with(service_uuid, { linked_to_service: [linked_to_service] })
|
121
|
-
|
122
|
-
subject.set_link(linked_service)
|
123
|
-
end
|
85
|
+
let(:this_service) { double(:this_service, uuid: service_uuid, resource_uri: "api/v1/#{service_uuid}") }
|
86
|
+
let(:linked_service) { double(:linked_service, uuid: linked_service_uuid, resource_uri: "api/v1/#{linked_service_uuid}", name: linked_service_name) }
|
87
|
+
|
88
|
+
it "updates the link attribute" do
|
89
|
+
expect(docker_cloud_services_api).to receive(:update).with(service_uuid, { linked_to_service: [linked_to_service] })
|
90
|
+
subject.set_link(linked_service, linked_service_internal_name)
|
124
91
|
end
|
92
|
+
end
|
125
93
|
|
126
|
-
|
127
|
-
|
128
|
-
|
94
|
+
describe "#stop" do
|
95
|
+
it "sends a request to stop the service" do
|
96
|
+
expect(docker_cloud_services_api).to receive(:stop).with(service_uuid)
|
129
97
|
|
130
|
-
|
131
|
-
end
|
98
|
+
subject.stop
|
132
99
|
end
|
133
100
|
end
|
134
101
|
end
|
@@ -1,141 +1,140 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
1
|
describe KumoDockerCloud::Stack do
|
4
|
-
let(:
|
5
|
-
let(:uuid) { 'foo' }
|
6
|
-
let(:service_name) { 'test_service' }
|
2
|
+
let(:stack) { described_class.new(app_name, environment_name) }
|
7
3
|
let(:app_name) { 'test_app' }
|
8
4
|
let(:environment_name) { 'environment' }
|
9
|
-
let(:
|
10
|
-
let(:client) { instance_double(DockerCloud::Client, stacks: stacks, services: service_api) }
|
11
|
-
let(:stacks) { double('stacks', all: [stack]) }
|
12
|
-
let(:stack_name) { "#{app_name}-#{environment_name}" }
|
13
|
-
let(:stack) { instance_double(DockerCloud::Stack, name: stack_name) }
|
14
|
-
let(:service) { instance_double(KumoDockerCloud::Service, uuid: uuid) }
|
15
|
-
let(:check_timeout) { 300 }
|
16
|
-
|
17
|
-
before do
|
18
|
-
allow(::DockerCloud::Client).to receive(:new).and_return(client)
|
19
|
-
allow(KumoDockerCloud::Service).to receive(:new).with(stack_name, service_name).and_return(service)
|
20
|
-
allow(service).to receive(:deploy).with("test_version")
|
21
|
-
end
|
5
|
+
let(:stack_name) { stack.stack_name }
|
22
6
|
|
23
7
|
describe '#deploy' do
|
24
|
-
|
8
|
+
let(:service_name) { 'test_service' }
|
9
|
+
let(:version) { '1' }
|
10
|
+
let(:checker) { instance_double(KumoDockerCloud::ServiceChecker, verify: nil) }
|
11
|
+
let(:service) { instance_double(KumoDockerCloud::Service) }
|
25
12
|
|
26
|
-
|
27
|
-
|
13
|
+
before do
|
14
|
+
allow(KumoDockerCloud::Service).to receive(:new).with(stack_name, service_name).and_return(service)
|
15
|
+
allow(service).to receive(:deploy).with(version)
|
28
16
|
end
|
29
17
|
|
30
|
-
|
31
|
-
expect { subject.deploy("", 1) }.to raise_error(KumoDockerCloud::Error, 'Service name cannot be empty')
|
32
|
-
end
|
18
|
+
subject { stack.deploy(service_name, version) }
|
33
19
|
|
34
|
-
it '
|
35
|
-
expect
|
20
|
+
it 'deploys the version of my service' do
|
21
|
+
expect(service).to receive(:deploy).with(version)
|
22
|
+
subject
|
36
23
|
end
|
37
24
|
|
38
|
-
|
39
|
-
|
40
|
-
|
25
|
+
context "validation" do
|
26
|
+
context "nil name" do
|
27
|
+
let(:service_name) { nil }
|
41
28
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
29
|
+
it "complains" do
|
30
|
+
expect { subject }.to raise_error(KumoDockerCloud::Error, 'Service name cannot be nil')
|
31
|
+
end
|
32
|
+
end
|
46
33
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
34
|
+
context "empty name" do
|
35
|
+
let(:service_name) { "" }
|
36
|
+
|
37
|
+
it "complains" do
|
38
|
+
expect { subject }.to raise_error(KumoDockerCloud::Error, 'Service name cannot be empty')
|
39
|
+
end
|
40
|
+
end
|
53
41
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
42
|
+
context "nil version" do
|
43
|
+
let(:version) { nil }
|
44
|
+
|
45
|
+
it "complains" do
|
46
|
+
expect { subject }.to raise_error(KumoDockerCloud::Error, 'Version cannot be nil')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "empty version" do
|
51
|
+
let(:version) { "" }
|
52
|
+
|
53
|
+
it "complains" do
|
54
|
+
expect { subject }.to raise_error(KumoDockerCloud::Error, 'Version cannot be empty')
|
55
|
+
end
|
56
|
+
end
|
60
57
|
end
|
61
58
|
|
59
|
+
context "with a checker supplied" do
|
60
|
+
subject { stack.deploy(service_name, version, checker) }
|
61
|
+
|
62
|
+
it 'uses the supplied service checker' do
|
63
|
+
expect(checker).to receive(:verify).with(service)
|
64
|
+
subject
|
65
|
+
end
|
66
|
+
end
|
62
67
|
end
|
63
68
|
|
64
69
|
describe '#deploy_blue_green' do
|
65
|
-
let(:
|
66
|
-
let(:
|
67
|
-
let(:
|
68
|
-
|
69
|
-
let(:
|
70
|
-
let(:
|
71
|
-
let(:
|
72
|
-
let(:
|
73
|
-
let(:
|
70
|
+
let(:service_name) { 'test_service' }
|
71
|
+
let(:version) { '1' }
|
72
|
+
let(:checker) { instance_double(KumoDockerCloud::ServiceChecker, verify: nil) }
|
73
|
+
|
74
|
+
let(:active_service) { instance_double(KumoDockerCloud::Service, :active_service, name: "service-a", stop: nil) }
|
75
|
+
let(:inactive_service) { instance_double(KumoDockerCloud::Service, :inactive_service, name: "service-b", deploy: nil) }
|
76
|
+
let(:service_link) { { name: switching_service_internal_link_name, to_service: linked_service_uri} }
|
77
|
+
let(:linked_service_uri) { "active_uri" }
|
78
|
+
let(:switching_service) { instance_double(KumoDockerCloud::Service, links: [service_link], set_link: nil) }
|
79
|
+
let(:switching_service_internal_link_name) { "app" }
|
74
80
|
let(:deploy_options) do
|
75
81
|
{
|
76
|
-
service_names: [
|
82
|
+
service_names: [active_service.name, inactive_service.name],
|
77
83
|
version: version,
|
78
|
-
|
79
|
-
|
80
|
-
switching_service_name: "nginx"
|
84
|
+
checker: checker,
|
85
|
+
switching_service_name: "switcher"
|
81
86
|
}
|
82
87
|
end
|
83
88
|
|
84
89
|
subject { described_class.new(app_name, environment_name).deploy_blue_green(deploy_options) }
|
85
90
|
|
86
91
|
before do
|
87
|
-
allow(KumoDockerCloud::Service).to receive(:
|
88
|
-
.with(stack_name, "service-a")
|
89
|
-
.and_return(service_a)
|
92
|
+
allow(KumoDockerCloud::Service).to receive(:service_by_resource_uri).with(linked_service_uri).and_return(active_service)
|
90
93
|
|
91
94
|
allow(KumoDockerCloud::Service).to receive(:new)
|
92
|
-
.with(stack_name,
|
93
|
-
.and_return(
|
95
|
+
.with(stack_name, active_service.name)
|
96
|
+
.and_return(active_service)
|
94
97
|
|
95
98
|
allow(KumoDockerCloud::Service).to receive(:new)
|
96
|
-
.with(stack_name,
|
97
|
-
.and_return(
|
99
|
+
.with(stack_name, inactive_service.name)
|
100
|
+
.and_return(inactive_service)
|
98
101
|
|
99
|
-
allow(
|
100
|
-
|
101
|
-
|
102
|
-
allow(service_a).to receive(:stop)
|
103
|
-
|
104
|
-
allow(service_b).to receive(:deploy).with(version)
|
105
|
-
allow(service_b).to receive(:check).with(deployment_checks, check_timeout)
|
102
|
+
allow(KumoDockerCloud::Service).to receive(:new)
|
103
|
+
.with(stack_name, deploy_options[:switching_service_name])
|
104
|
+
.and_return(switching_service)
|
106
105
|
end
|
107
106
|
|
108
107
|
context 'when parameters are missing' do
|
109
|
-
it '
|
108
|
+
it 'blows up when version is missing' do
|
110
109
|
deploy_options.delete(:version)
|
111
110
|
expect{ subject }.to raise_error(KumoDockerCloud::Error, "Version cannot be nil")
|
112
111
|
end
|
113
112
|
|
114
|
-
it '
|
113
|
+
it 'blows up when service_names are missing' do
|
115
114
|
deploy_options.delete(:service_names)
|
116
115
|
expect{ subject }.to raise_error(KumoDockerCloud::Error, "Service names cannot be nil")
|
117
116
|
end
|
118
117
|
|
119
|
-
it '
|
118
|
+
it 'blows up when switching_service_name is missing' do
|
120
119
|
deploy_options.delete(:switching_service_name)
|
121
120
|
expect{ subject }.to raise_error(KumoDockerCloud::Error, "Switching service name cannot be nil")
|
122
121
|
end
|
123
122
|
end
|
124
123
|
|
125
124
|
it 'deploys to the blue service only' do
|
126
|
-
expect(
|
127
|
-
expect(
|
128
|
-
expect(
|
125
|
+
expect(inactive_service).to receive(:deploy).with(version)
|
126
|
+
expect(checker).to receive(:verify).with(inactive_service)
|
127
|
+
expect(active_service).to_not receive(:deploy)
|
129
128
|
subject
|
130
129
|
end
|
131
130
|
|
132
131
|
it 'switches over to the blue service on a successful deployment' do
|
133
|
-
expect(
|
132
|
+
expect(switching_service).to receive(:set_link).with(inactive_service, switching_service_internal_link_name)
|
134
133
|
subject
|
135
134
|
end
|
136
135
|
|
137
136
|
it 'shuts down the previously green service' do
|
138
|
-
expect(
|
137
|
+
expect(active_service).to receive(:stop)
|
139
138
|
subject
|
140
139
|
end
|
141
140
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kumo_dockercloud
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Redbubble
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2016-
|
13
|
+
date: 2016-05-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: httpi
|
@@ -134,6 +134,7 @@ files:
|
|
134
134
|
- lib/kumo_dockercloud/environment_config.rb
|
135
135
|
- lib/kumo_dockercloud/errors.rb
|
136
136
|
- lib/kumo_dockercloud/service.rb
|
137
|
+
- lib/kumo_dockercloud/service_checker.rb
|
137
138
|
- lib/kumo_dockercloud/stack.rb
|
138
139
|
- lib/kumo_dockercloud/stack_file.rb
|
139
140
|
- lib/kumo_dockercloud/state_validator.rb
|
@@ -149,6 +150,7 @@ files:
|
|
149
150
|
- spec/kumo_dockercloud/docker_cloud_api_spec.rb
|
150
151
|
- spec/kumo_dockercloud/environment_config_spec.rb
|
151
152
|
- spec/kumo_dockercloud/environment_spec.rb
|
153
|
+
- spec/kumo_dockercloud/service_checker_spec.rb
|
152
154
|
- spec/kumo_dockercloud/service_spec.rb
|
153
155
|
- spec/kumo_dockercloud/stack_file_spec.rb
|
154
156
|
- spec/kumo_dockercloud/stack_spec.rb
|
@@ -187,6 +189,7 @@ test_files:
|
|
187
189
|
- spec/kumo_dockercloud/docker_cloud_api_spec.rb
|
188
190
|
- spec/kumo_dockercloud/environment_config_spec.rb
|
189
191
|
- spec/kumo_dockercloud/environment_spec.rb
|
192
|
+
- spec/kumo_dockercloud/service_checker_spec.rb
|
190
193
|
- spec/kumo_dockercloud/service_spec.rb
|
191
194
|
- spec/kumo_dockercloud/stack_file_spec.rb
|
192
195
|
- spec/kumo_dockercloud/stack_spec.rb
|