kumo_dockercloud 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/kumo_dockercloud/service.rb +29 -5
- data/lib/kumo_dockercloud/stack.rb +23 -0
- data/lib/kumo_dockercloud/stack_file.rb +1 -4
- data/lib/kumo_dockercloud/version.rb +1 -1
- data/spec/kumo_dockercloud/service_spec.rb +71 -9
- data/spec/kumo_dockercloud/stack_file_spec.rb +0 -31
- data/spec/kumo_dockercloud/stack_spec.rb +79 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f17aa8666415f9aca28a1bff56fd4f15008ac281
|
4
|
+
data.tar.gz: 69a0f07ebf17b9a59fdf426a7e567f620f862b88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4c7cec1ea8cce00a0ac8cd85375ec7ab965c44f02fd9516f0be9e0cb4598e9fbab4bb1d4034f406c4e845f71b76f27ca20e0f371b5c39a7ef4a7b9daba90fed
|
7
|
+
data.tar.gz: a06f463691c216e733f47d83f16713ce85706f5befff163f97bc11d8d52689ecdd72a04271afb328446a7aa009a9870cf4186f3c632751f3641d4a597dbe986d
|
@@ -2,6 +2,8 @@ require 'timeout'
|
|
2
2
|
|
3
3
|
module KumoDockerCloud
|
4
4
|
class Service
|
5
|
+
attr_reader :name
|
6
|
+
|
5
7
|
def initialize(stack_name, service_name)
|
6
8
|
@stack_name = stack_name
|
7
9
|
@name = service_name
|
@@ -35,11 +37,33 @@ module KumoDockerCloud
|
|
35
37
|
raise KumoDockerCloud::ServiceDeployError.new("One or more checks failed to pass within the timeout")
|
36
38
|
end
|
37
39
|
|
40
|
+
def links
|
41
|
+
get_service.linked_to_service.map { |service| KumoDockerCloud::Service.new(stack_name, service[:name]) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_link(service_to_link)
|
45
|
+
linked_service = {
|
46
|
+
to_service: service_to_link.resource_uri,
|
47
|
+
name: service_to_link.name,
|
48
|
+
from_service: resource_uri
|
49
|
+
}
|
50
|
+
|
51
|
+
docker_cloud_api.services.update(uuid, linked_to_service: [linked_service])
|
52
|
+
end
|
53
|
+
|
54
|
+
def stop
|
55
|
+
docker_cloud_api.services.stop(uuid)
|
56
|
+
end
|
57
|
+
|
58
|
+
def resource_uri
|
59
|
+
get_service.resource_uri
|
60
|
+
end
|
61
|
+
|
38
62
|
private
|
39
|
-
attr_reader :stack_name
|
63
|
+
attr_reader :stack_name
|
40
64
|
|
41
65
|
def containers
|
42
|
-
|
66
|
+
get_service.containers
|
43
67
|
end
|
44
68
|
|
45
69
|
def update_image(version)
|
@@ -58,16 +82,16 @@ module KumoDockerCloud
|
|
58
82
|
@docker_cloud_api ||= KumoDockerCloud::DockerCloudApi.new
|
59
83
|
end
|
60
84
|
|
61
|
-
def
|
85
|
+
def get_service
|
62
86
|
docker_cloud_api.service_by_stack_and_service_name(stack_name, name)
|
63
87
|
end
|
64
88
|
|
65
89
|
def uuid
|
66
|
-
|
90
|
+
get_service.uuid
|
67
91
|
end
|
68
92
|
|
69
93
|
def image_name
|
70
|
-
|
94
|
+
get_service.image_name.split(':').first
|
71
95
|
end
|
72
96
|
end
|
73
97
|
end
|
@@ -2,6 +2,7 @@ module KumoDockerCloud
|
|
2
2
|
class Stack
|
3
3
|
attr_reader :stack_name, :app_name, :options
|
4
4
|
|
5
|
+
#TODO delete options
|
5
6
|
def initialize(app_name, env_name, options = { contactable: true })
|
6
7
|
@app_name = app_name
|
7
8
|
@stack_name = "#{app_name}-#{env_name}"
|
@@ -17,6 +18,28 @@ module KumoDockerCloud
|
|
17
18
|
service.check(checks, check_timeout) if checks
|
18
19
|
end
|
19
20
|
|
21
|
+
def deploy_blue_green(options)
|
22
|
+
service_names = options[:service_names]
|
23
|
+
version = options[:version]
|
24
|
+
checks = options[:checks]
|
25
|
+
check_timeout = options[:check_timeout]
|
26
|
+
switching_service_name = options[:switching_service_name]
|
27
|
+
|
28
|
+
validate_params(version, "Version")
|
29
|
+
validate_params(service_names, "Service names")
|
30
|
+
validate_params(switching_service_name, "Switching service name")
|
31
|
+
|
32
|
+
services = service_names.map { |service_name| Service.new(stack_name, service_name) }
|
33
|
+
|
34
|
+
switching_service = Service.new(stack_name, switching_service_name)
|
35
|
+
green_service = switching_service.links.find { |linked_service| services.find { |service| service.name == linked_service.name } }
|
36
|
+
blue_service = services.find { |service| service.name != green_service.name }
|
37
|
+
|
38
|
+
deploy(blue_service.name, version, checks, check_timeout)
|
39
|
+
switching_service.set_link(blue_service)
|
40
|
+
green_service.stop
|
41
|
+
end
|
42
|
+
|
20
43
|
private
|
21
44
|
|
22
45
|
def validate_params(param_value, param_name)
|
@@ -5,14 +5,11 @@ module KumoDockerCloud
|
|
5
5
|
def self.create_from_template(stack_template, config, env_vars)
|
6
6
|
parsed = YAML.load(ERB.new(stack_template).result(config.get_binding))
|
7
7
|
|
8
|
-
parsed[config.app_name]['environment'] ||= {}
|
9
|
-
parsed[config.app_name]['environment'].merge!(config.plain_text_secrets)
|
10
|
-
parsed[config.app_name]['environment'].merge!(env_vars.fetch(config.app_name, {}))
|
11
|
-
|
12
8
|
converted_env_vars = make_all_root_level_keys_strings(env_vars)
|
13
9
|
|
14
10
|
env_vars.each do |key, _|
|
15
11
|
key_string = key.to_s
|
12
|
+
parsed[key_string]['environment'] ||= {}
|
16
13
|
parsed[key_string]['environment'].merge!(converted_env_vars.fetch(key_string))
|
17
14
|
end
|
18
15
|
|
@@ -4,32 +4,33 @@ require 'httpi'
|
|
4
4
|
describe KumoDockerCloud::Service do
|
5
5
|
let(:service_image) { "repository/docker_image_name:version" }
|
6
6
|
let(:service_uuid) { "i_am_a_unique_snowflower" }
|
7
|
-
let(:
|
8
|
-
let(:
|
7
|
+
let(:docker_cloud_service) { double(:service, uuid: service_uuid, image_name: service_image, containers: [], resource_uri: "api/v1/#{service_uuid}")}
|
8
|
+
let(:docker_cloud_services_api) { double(:services_api) }
|
9
|
+
let(:docker_cloud_api) { instance_double(KumoDockerCloud::DockerCloudApi, services: docker_cloud_services_api)}
|
9
10
|
|
10
11
|
subject { described_class.new('stack_name', 'service_name') }
|
11
12
|
|
12
13
|
before do
|
13
14
|
allow(KumoDockerCloud::DockerCloudApi).to receive(:new).and_return(docker_cloud_api)
|
14
15
|
|
15
|
-
allow(docker_cloud_api).to receive(:service_by_stack_and_service_name).with('stack_name', 'service_name').and_return(
|
16
|
+
allow(docker_cloud_api).to receive(:service_by_stack_and_service_name).with('stack_name', 'service_name').and_return(docker_cloud_service)
|
16
17
|
end
|
17
18
|
|
18
19
|
describe '#deploy' do
|
19
20
|
it 'runs the actual update and redeploy methods' do
|
20
|
-
expect(
|
21
|
-
expect(
|
21
|
+
expect(docker_cloud_services_api).to receive(:update).with(service_uuid, { image: service_image })
|
22
|
+
expect(docker_cloud_services_api).to receive(:redeploy).with(service_uuid)
|
22
23
|
subject.deploy('version')
|
23
24
|
end
|
24
25
|
|
25
26
|
it 'raises an appropriate exception when there is an error during image update' do
|
26
|
-
expect(
|
27
|
+
expect(docker_cloud_services_api).to receive(:update).and_raise(RestClient::InternalServerError)
|
27
28
|
expect { subject.deploy('version') }.to raise_error(KumoDockerCloud::ServiceDeployError, "Something went wrong during service update on Docker Cloud's end")
|
28
29
|
end
|
29
30
|
|
30
31
|
it 'raises an appropriate exception when there is an error during redployment' do
|
31
|
-
allow(
|
32
|
-
expect(
|
32
|
+
allow(docker_cloud_services_api).to receive(:update).with(service_uuid, { image: service_image })
|
33
|
+
expect(docker_cloud_services_api).to receive(:redeploy).and_raise(RestClient::InternalServerError)
|
33
34
|
expect { subject.deploy('version') }.to raise_error(KumoDockerCloud::ServiceDeployError, "Something went wrong during service update on Docker Cloud's end")
|
34
35
|
end
|
35
36
|
end
|
@@ -54,7 +55,7 @@ describe KumoDockerCloud::Service do
|
|
54
55
|
allow(http_lib).to receive(:get).with("http://whale1.test/site_status").and_return(200)
|
55
56
|
allow(http_lib).to receive(:get).with("http://whale2.test/site_status").and_return("timeout", "timeout", 200)
|
56
57
|
allow(whale1).to receive(:state).and_return("Starting", "Running")
|
57
|
-
allow(
|
58
|
+
allow(docker_cloud_service).to receive(:containers).and_return(containers)
|
58
59
|
end
|
59
60
|
|
60
61
|
it 'resolves to true if all the checks eventually pass' do
|
@@ -68,5 +69,66 @@ describe KumoDockerCloud::Service do
|
|
68
69
|
allow(whale1).to receive(:state).and_return("Starting")
|
69
70
|
expect { subject.check(checks, short_timeout) }.to raise_error(KumoDockerCloud::ServiceDeployError, "One or more checks failed to pass within the timeout")
|
70
71
|
end
|
72
|
+
|
73
|
+
describe "#links" do
|
74
|
+
let(:linked_service_uuid) { "i_am_the_db" }
|
75
|
+
let(:linked_service_name) { "db" }
|
76
|
+
let(:linked_to_service) do
|
77
|
+
{
|
78
|
+
from_service: service_uuid,
|
79
|
+
name: linked_service_name,
|
80
|
+
to_service: linked_service_uuid
|
81
|
+
}
|
82
|
+
end
|
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
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "#set_link" do
|
101
|
+
let(:linked_service_uuid) { "i_am_the_db" }
|
102
|
+
let(:linked_service_name) { "db" }
|
103
|
+
let(:linked_to_service) do
|
104
|
+
{
|
105
|
+
to_service: "api/v1/#{linked_service_uuid}",
|
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
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "#stop" do
|
127
|
+
it "sends a request to stop the service" do
|
128
|
+
expect(docker_cloud_services_api).to receive(:stop).with(service_uuid)
|
129
|
+
|
130
|
+
subject.stop
|
131
|
+
end
|
132
|
+
end
|
71
133
|
end
|
72
134
|
end
|
@@ -32,8 +32,6 @@ describe KumoDockerCloud::StackFile do
|
|
32
32
|
expect(subject).to eq(app_name => {
|
33
33
|
'image' => 'a-thing',
|
34
34
|
'environment' => {
|
35
|
-
'TEST_ENV' => 'FAKE',
|
36
|
-
'MORE' => 'ANOTHER',
|
37
35
|
'KEY' => 'VALUE'
|
38
36
|
}
|
39
37
|
})
|
@@ -53,8 +51,6 @@ describe KumoDockerCloud::StackFile do
|
|
53
51
|
'image' => 'a-thing',
|
54
52
|
'environment' => {
|
55
53
|
'TEST' => 'thing',
|
56
|
-
'TEST_ENV' => 'FAKE',
|
57
|
-
'MORE' => 'ANOTHER',
|
58
54
|
'KEY' => 'VALUE'
|
59
55
|
}
|
60
56
|
})
|
@@ -72,37 +68,12 @@ describe KumoDockerCloud::StackFile do
|
|
72
68
|
expect(subject).to eq(app_name => {
|
73
69
|
'image' => 'a-thing',
|
74
70
|
'environment' => {
|
75
|
-
'TEST_ENV' => 'FAKE',
|
76
|
-
'MORE' => 'ANOTHER',
|
77
71
|
'KEY' => 'VALUE'
|
78
72
|
}
|
79
73
|
})
|
80
74
|
end
|
81
75
|
end
|
82
76
|
|
83
|
-
context 'no app name env var' do
|
84
|
-
let(:env_vars) do
|
85
|
-
{ }
|
86
|
-
end
|
87
|
-
|
88
|
-
let(:stack_template) do
|
89
|
-
<<-eos
|
90
|
-
application-stack-name:
|
91
|
-
image: a-thing
|
92
|
-
eos
|
93
|
-
end
|
94
|
-
|
95
|
-
it 'should create the environment with secrets in it' do
|
96
|
-
expect(subject).to eq(app_name => {
|
97
|
-
'image' => 'a-thing',
|
98
|
-
'environment' => {
|
99
|
-
'TEST_ENV' => 'FAKE',
|
100
|
-
'MORE' => 'ANOTHER'
|
101
|
-
}
|
102
|
-
})
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
77
|
context 'with other services' do
|
107
78
|
let(:env_vars) do
|
108
79
|
{
|
@@ -130,8 +101,6 @@ describe KumoDockerCloud::StackFile do
|
|
130
101
|
app_name => {
|
131
102
|
'image' => 'a-thing',
|
132
103
|
'environment' => {
|
133
|
-
'TEST_ENV' => 'FAKE',
|
134
|
-
'MORE' => 'ANOTHER',
|
135
104
|
'KEY' => 'VALUE'
|
136
105
|
}},
|
137
106
|
'another_service' => {
|
@@ -60,4 +60,83 @@ describe KumoDockerCloud::Stack do
|
|
60
60
|
end
|
61
61
|
|
62
62
|
end
|
63
|
+
|
64
|
+
describe '#deploy_blue_green' do
|
65
|
+
let(:service_a_uuid) { 'service_a_uuid' }
|
66
|
+
let(:service_a) { instance_double(KumoDockerCloud::Service, :service_a, uuid: uuid, name: "service-a") }
|
67
|
+
let(:service_b_uuid) { 'service_b_uuid' }
|
68
|
+
let(:service_b) { instance_double(KumoDockerCloud::Service, :service_b, uuid: uuid, name: "service-b") }
|
69
|
+
let(:nginx) { instance_double(KumoDockerCloud::Service, uuid: "nginx_uuid") }
|
70
|
+
let(:version) { "1" }
|
71
|
+
let(:deployment_checks) { [] }
|
72
|
+
let(:links) { [service_a] }
|
73
|
+
let(:check_timeout) { 120 }
|
74
|
+
let(:deploy_options) do
|
75
|
+
{
|
76
|
+
service_names: ["service-a", "service-b"],
|
77
|
+
version: version,
|
78
|
+
checks: deployment_checks,
|
79
|
+
check_timeout: check_timeout,
|
80
|
+
switching_service_name: "nginx"
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
subject { described_class.new(app_name, environment_name).deploy_blue_green(deploy_options) }
|
85
|
+
|
86
|
+
before do
|
87
|
+
allow(KumoDockerCloud::Service).to receive(:new)
|
88
|
+
.with(stack_name, "service-a")
|
89
|
+
.and_return(service_a)
|
90
|
+
|
91
|
+
allow(KumoDockerCloud::Service).to receive(:new)
|
92
|
+
.with(stack_name, "service-b")
|
93
|
+
.and_return(service_b)
|
94
|
+
|
95
|
+
allow(KumoDockerCloud::Service).to receive(:new)
|
96
|
+
.with(stack_name, "nginx")
|
97
|
+
.and_return(nginx)
|
98
|
+
|
99
|
+
allow(nginx).to receive(:links).and_return(links)
|
100
|
+
allow(nginx).to receive(:set_link).with(service_b)
|
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)
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'when parameters are missing' do
|
109
|
+
it 'blow up when version is missing' do
|
110
|
+
deploy_options.delete(:version)
|
111
|
+
expect{ subject }.to raise_error(KumoDockerCloud::Error, "Version cannot be nil")
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'blow up when service_names are missing' do
|
115
|
+
deploy_options.delete(:service_names)
|
116
|
+
expect{ subject }.to raise_error(KumoDockerCloud::Error, "Service names cannot be nil")
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'blow up when switching_service_name is missing' do
|
120
|
+
deploy_options.delete(:switching_service_name)
|
121
|
+
expect{ subject }.to raise_error(KumoDockerCloud::Error, "Switching service name cannot be nil")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'deploys to the blue service only' do
|
126
|
+
expect(service_b).to receive(:deploy).with(version)
|
127
|
+
expect(service_b).to receive(:check).with(deployment_checks, check_timeout)
|
128
|
+
expect(service_a).to_not receive(:deploy)
|
129
|
+
subject
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'switches over to the blue service on a successful deployment' do
|
133
|
+
expect(nginx).to receive(:set_link).with(service_b)
|
134
|
+
subject
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'shuts down the previously green service' do
|
138
|
+
expect(service_a).to receive(:stop)
|
139
|
+
subject
|
140
|
+
end
|
141
|
+
end
|
63
142
|
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:
|
4
|
+
version: 2.0.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-04-
|
13
|
+
date: 2016-04-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: httpi
|