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 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