kumo_dockercloud 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|