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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f17aa8666415f9aca28a1bff56fd4f15008ac281
4
- data.tar.gz: 69a0f07ebf17b9a59fdf426a7e567f620f862b88
3
+ metadata.gz: 2582ba0407f7bdd51b0a313b93e9508719ce48c0
4
+ data.tar.gz: 9d1e586ca6c319a3b9db096d571b772d8c026d48
5
5
  SHA512:
6
- metadata.gz: c4c7cec1ea8cce00a0ac8cd85375ec7ab965c44f02fd9516f0be9e0cb4598e9fbab4bb1d4034f406c4e845f71b76f27ca20e0f371b5c39a7ef4a7b9daba90fed
7
- data.tar.gz: a06f463691c216e733f47d83f16713ce85706f5befff163f97bc11d8d52689ecdd72a04271afb328446a7aa009a9870cf4186f3c632751f3641d4a597dbe986d
6
+ metadata.gz: c67e36f20cd1691b05d0fad87f9133a38f33d3a4a9efda200a213e318b8fe2b13a4474c54df70b5cbdcbaa4e59a103ec19f8103e8d7082dd0bf6ea8efb669e48
7
+ data.tar.gz: 5109665c78f88ff6af562c82d0030d37696068737474e531f2848a970b0d5f62ba59397e00dc2ac21dd81c231b6f8865067f96e00998e1eba04b36a9b616ca12
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
+ --require spec_helper
1
2
  --format documentation
2
3
  --color
@@ -2,4 +2,5 @@ require 'kumo_dockercloud/environment'
2
2
  require 'kumo_dockercloud/deployment'
3
3
  require 'kumo_dockercloud/stack'
4
4
  require 'kumo_dockercloud/service'
5
+ require 'kumo_dockercloud/service_checker'
5
6
  require 'kumo_dockercloud/errors'
@@ -4,7 +4,7 @@ require 'base64' # left out of DockerCloud gem
4
4
  module KumoDockerCloud
5
5
  class DockerCloudApi
6
6
  extend Forwardable
7
- def_delegator :@client, :services
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 check(checks, timeout)
18
- Timeout::timeout(timeout) do
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.map { |service| KumoDockerCloud::Service.new(stack_name, service[:name]) }
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: service_to_link.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, checks = nil, check_timeout = 300)
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
- service.check(checks, check_timeout) if checks
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
- checks = options[:checks]
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
- 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 }
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
- deploy(blue_service.name, version, checks, check_timeout)
39
- switching_service.set_link(blue_service)
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,3 +1,3 @@
1
1
  module KumoDockerCloud
2
- VERSION = '2.0.0'
2
+ VERSION = '2.1.0'
3
3
  end
@@ -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: 'application-stack-name', env_name: env_name, config_path: config_path) }
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
- describe '#plain_text_secrets' do
27
- context 'with no encrypted secrets' do
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
- let(:string) { 'plain_text_secrets' }
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
- it do
38
- expect(subject).to eq secrets_data
39
- end
37
+ it do
38
+ expect(subject).to eq secrets_data
40
39
  end
40
+ end
41
41
 
42
- context 'with some encrypted secrets' do
43
- let(:env_name) { 'test_encrypted' }
44
- let(:plain_value) { 'otherval' }
42
+ context 'with some encrypted secrets' do
43
+ let(:env_name) { 'test_encrypted' }
44
+ let(:plain_value) { 'otherval' }
45
45
 
46
- let(:kms) { double('KumoKi::KMS') }
46
+ let(:kms) { double('KumoKi::KMS') }
47
47
 
48
- let(:plain_data) do
49
- {
50
- 'testkey' => 'someval',
51
- 'enctest' => plain_value
52
- }
53
- end
48
+ let(:plain_data) do
49
+ {
50
+ 'testkey' => 'someval',
51
+ 'enctest' => plain_value
52
+ }
53
+ end
54
54
 
55
- let(:string) { 'plain_text_secrets' }
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
- before do
58
- allow(KumoKi::KMS).to receive(:new).and_return(kms)
59
- allow(kms).to receive(:decrypt).with('ZW5jcnlwdGVkIGJpbmFyeSBkYXRh').and_return(plain_value)
60
- end
60
+ it do
61
+ expect(subject).to eq plain_data
62
+ end
63
+ end
64
+ end
61
65
 
62
- it do
63
- expect(subject).to eq plain_data
64
- end
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 'with API mocked' do
69
- before do
70
- allow(KumoDockerCloud::DockerCloudApi).to receive(:new).and_return docker_cloud_api
71
- allow(docker_cloud_api).to receive(:services_by_stack_name).with("application-stack-name-#{env_name}").and_return(services_for_stack)
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
- describe '#image_name' do
75
- let(:string) { 'image_name' }
90
+ end
76
91
 
77
- context 'when there is a service for the given stack' do
78
- let(:image_name) { 'redbubble/application-stack-name:1234' }
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
- it 'uses the pre-exisiting image name' do
82
- expect(subject).to eq image_name
83
- end
84
- end
95
+ describe '#image_name' do
96
+ subject { instance.image_name }
85
97
 
86
- context 'when there is no service for the given stack' do
87
- let(:services_for_stack) { [] }
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
- it { expect(subject).to eq 'redbubble/application-stack-name:master' }
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
- describe '#image_tag' do
94
- let(:services_for_stack) { [ double(DockerCloud::Service, image_name: image_name)] }
107
+ context 'when there is no service for the given stack' do
108
+ let(:services_for_stack) { [] }
95
109
 
96
- subject { instance.image_tag }
110
+ it { expect(subject).to eq "redbubble/#{app_name}:master" }
111
+ end
112
+ end
97
113
 
98
- context "when the image name is 'redbubble/assete-wala:latest'" do
99
- let(:image_name) { 'redbubble/application-stack-name:latest' }
100
- it { expect(subject).to eq 'latest' }
101
- end
102
- context "when the image name is 'some-registry/mything:9999'" do
103
- let(:image_name) { 'some-registry/mything:9999' }
104
- it { expect(subject).to eq '9999' }
105
- end
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
@@ -1,6 +1,3 @@
1
- require 'kumo_dockercloud/environment'
2
- require 'kumo_dockercloud/environment_config'
3
-
4
1
  describe KumoDockerCloud::Environment do
5
2
  let(:env_vars) { {app_name => {'KEY' => 'VALUE'}} }
6
3
  let(:app_name) { 'application-stack-name' }
@@ -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(:docker_cloud_api) { instance_double(KumoDockerCloud::DockerCloudApi, services: docker_cloud_services_api)}
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 '#check' do
39
- let(:http_lib) { double('http_lib') }
40
- let(:container_status_check) { lambda { |container| container.state == 'Running' } }
41
- let(:endpoint_check) do
42
- lambda do |container|
43
- url = "#{container.container_ports.first[:endpoint_uri]}/site_status"
44
- response = http_lib.get(url)
45
- response == 200
46
- end
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(:checks) {[container_status_check, endpoint_check]}
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(http_lib).to receive(:get).with("http://whale1.test/site_status").and_return(200)
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 'resolves to true if all the checks eventually pass' do
62
- allow(subject).to receive(:sleep).and_return(nil)
63
- expect(subject.check(checks, check_timeout)).to eq(true)
64
- end
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 'raises an error if any check fails to pass within the timeout period' do
68
- short_timeout = 2
69
- allow(whale1).to receive(:state).and_return("Starting")
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
- 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
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
- 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
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
- 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)
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
- subject.stop
131
- end
98
+ subject.stop
132
99
  end
133
100
  end
134
101
  end
@@ -1,6 +1,3 @@
1
- require_relative '../../lib/kumo_dockercloud/environment_config'
2
- require_relative '../../lib/kumo_dockercloud/stack_file'
3
-
4
1
  describe KumoDockerCloud::StackFile do
5
2
 
6
3
  let(:app_name) { 'application-stack-name' }
@@ -1,141 +1,140 @@
1
- require 'spec_helper'
2
-
3
1
  describe KumoDockerCloud::Stack do
4
- let(:service_api) { instance_double(DockerCloud::ServiceAPI) }
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(:app_version) { '1' }
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
- subject { described_class.new(app_name, environment_name) }
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
- it 'complains if passed a nil service name' do
27
- expect { subject.deploy(nil, 1) }.to raise_error(KumoDockerCloud::Error, 'Service name cannot be nil')
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
- it 'complains if passed an empty service name' do
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 'complains if passed a nil version' do
35
- expect { subject.deploy("test_service", nil) }.to raise_error(KumoDockerCloud::Error, 'Version cannot be nil')
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
- it 'complains if passed an empty version' do
39
- expect { subject.deploy("test_service", "") }.to raise_error(KumoDockerCloud::Error, 'Version cannot be empty')
40
- end
25
+ context "validation" do
26
+ context "nil name" do
27
+ let(:service_name) { nil }
41
28
 
42
- it 'deploys the version of my service' do
43
- expect(service).to receive(:deploy).with("test_version")
44
- subject.deploy('test_service', 'test_version')
45
- end
29
+ it "complains" do
30
+ expect { subject }.to raise_error(KumoDockerCloud::Error, 'Service name cannot be nil')
31
+ end
32
+ end
46
33
 
47
- it 'passes any checks to the checker' do
48
- checks = ["check1", "check2"]
49
- allow(service).to receive(:deploy).with("test_version")
50
- expect(service).to receive(:check).with(checks, check_timeout)
51
- subject.deploy('test_service', 'test_version', checks)
52
- end
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
- it 'passes any specific timeout to the checker' do
55
- checks = ["check1", "check2"]
56
- shortened_timeout = 10
57
- allow(service).to receive(:deploy).with("test_version")
58
- expect(service).to receive(:check).with(checks, shortened_timeout)
59
- subject.deploy('test_service', 'test_version', checks, shortened_timeout)
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(: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 }
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: ["service-a", "service-b"],
82
+ service_names: [active_service.name, inactive_service.name],
77
83
  version: version,
78
- checks: deployment_checks,
79
- check_timeout: check_timeout,
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(:new)
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, "service-b")
93
- .and_return(service_b)
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, "nginx")
97
- .and_return(nginx)
99
+ .with(stack_name, inactive_service.name)
100
+ .and_return(inactive_service)
98
101
 
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)
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 'blow up when version is missing' do
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 'blow up when service_names are missing' do
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 'blow up when switching_service_name is missing' do
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(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)
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(nginx).to receive(:set_link).with(service_b)
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(service_a).to receive(:stop)
137
+ expect(active_service).to receive(:stop)
139
138
  subject
140
139
  end
141
140
  end
@@ -1,6 +1,3 @@
1
- require 'rspec'
2
- require 'spec_helper'
3
-
4
1
  describe KumoDockerCloud::StateValidator do
5
2
  describe '#wait_for_state' do
6
3
  subject { state_validator.wait_for_state('done', 1) }
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.0.0
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-04-27 00:00:00.000000000 Z
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