kumo_dockercloud 1.0.0 → 1.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: 30cba0c29df6e2bd9db7bae50838fc0bf317c2fa
4
- data.tar.gz: e5b9bf55b8ff0cf329e6537099db2ea54840d383
3
+ metadata.gz: 8c1c50a0d06325de8c9767de8b5e85a4d09dd56d
4
+ data.tar.gz: b94cfc7050e86e202425c77f8e7bc492db9b5a9b
5
5
  SHA512:
6
- metadata.gz: 926d693084ea08c039a9fcd643fc388399d771e73b9d221d0f4ac6a82734382dbf1090c025b1b7809e4056c514fa9810c0e4d2ad240ceabf53de7ee29dc78a41
7
- data.tar.gz: db132f216e112a0a15cf6cec3563bc340f63f2cd065b0177d1a2a19bf5d127556270c93f81981cbb4e96ecca1e82c4170611357e460b634197a371299460d40f
6
+ metadata.gz: 317f70cb98f1dfc376cbd258a7a7a55ae6c64cba9b9f0a15e50d146d0c1f12f154f64caecb6c38193cea049aed18be586ec6e2ef0f8a9b46a8be18458d8d26eb
7
+ data.tar.gz: fc7f5ea565530532030617f44306afe62aa4da2cee3bbb369c2d882a4d076fda5b1bf1e3810544ed8b308ca3f085daab6b9c99f6265b4cb005037300858f94e5
@@ -1,3 +1,5 @@
1
1
  require 'kumo_dockercloud/environment'
2
2
  require 'kumo_dockercloud/deployment'
3
3
  require 'kumo_dockercloud/stack'
4
+ require 'kumo_dockercloud/service'
5
+ require 'kumo_dockercloud/errors'
@@ -35,11 +35,20 @@ module KumoDockerCloud
35
35
  StateValidator.new(service_state_provider).wait_for_state('Running', 240)
36
36
  end
37
37
 
38
+ def wait_for_exit_state
39
+ exit_state_provider = lambda {
40
+ service = docker_cloud_api.services.get(service_uuid)
41
+ { name: service.name, exit_code: service.containers.first.exit_code }
42
+ }
43
+
44
+ StateValidator.new(exit_state_provider).wait_for_exit_state(240)
45
+ end
46
+
38
47
  private
39
48
 
40
- def service_uuid
49
+ def service_uuid(service_name)
41
50
  @service_uuid ||= begin
42
- services = docker_cloud_api.services_by_stack_name(stack_name)
51
+ services = docker_cloud_api.service_by_stack_and_service_name(stack_name, service_name)
43
52
  services.first.uuid
44
53
  end
45
54
  end
@@ -120,7 +129,7 @@ module KumoDockerCloud
120
129
  raise e
121
130
  else
122
131
  retry
123
- end
132
+ end
124
133
  end
125
134
  end
126
135
  end
@@ -9,7 +9,8 @@ module KumoDockerCloud
9
9
  def initialize(options = {})
10
10
  options[:username] ||= ENV['DOCKERCLOUD_USER']
11
11
  options[:api_key] ||= ENV['DOCKERCLOUD_APIKEY']
12
- @client = options[:client] || ::DockerCloud::Client.new(options[:username], options[:api_key])
12
+
13
+ @client = options[:client] || ::DockerCloud::Client.new(options.fetch(:username), options.fetch(:api_key))
13
14
  end
14
15
 
15
16
  def stack_by_name(name)
@@ -22,6 +23,11 @@ module KumoDockerCloud
22
23
  stack.services
23
24
  end
24
25
 
26
+ def service_by_stack_and_service_name(stack_name, service_name)
27
+ services = services_by_stack_name(stack_name)
28
+ services.find { |s| s.name == service_name }
29
+ end
30
+
25
31
  def containers_by_stack_name(stack_name)
26
32
  services_by_stack_name(stack_name).collect do |service|
27
33
  service.containers
@@ -8,6 +8,7 @@ require_relative 'state_validator'
8
8
  require_relative 'environment_config'
9
9
  require_relative 'stack_file'
10
10
 
11
+ #TODO refactor this to use the new checker inside Service
11
12
  module KumoDockerCloud
12
13
  class Environment
13
14
  extend ::Forwardable
@@ -0,0 +1,4 @@
1
+ module KumoDockerCloud
2
+ class Error < RuntimeError; end
3
+ class ServiceDeployError < RuntimeError; end
4
+ end
@@ -0,0 +1,73 @@
1
+ require 'timeout'
2
+
3
+ module KumoDockerCloud
4
+ class Service
5
+ def initialize(stack_name, service_name)
6
+ @stack_name = stack_name
7
+ @name = service_name
8
+ end
9
+
10
+ def deploy(version)
11
+ update_image(version)
12
+ redeploy
13
+ end
14
+
15
+ def check(checks, timeout)
16
+ Timeout::timeout(timeout) do
17
+ all_tests_passed = true
18
+ containers.each do |container|
19
+ checks.each do |check|
20
+ unless check.call(container)
21
+ all_tests_passed = false
22
+ end
23
+ end
24
+ end
25
+
26
+ unless all_tests_passed
27
+ print '.'
28
+ sleep(5)
29
+ check(checks, timeout)
30
+ end
31
+
32
+ true
33
+ end
34
+ rescue
35
+ raise KumoDockerCloud::ServiceDeployError.new("One or more checks failed to pass within the timeout")
36
+ end
37
+
38
+ private
39
+ attr_reader :stack_name, :name
40
+
41
+ def containers
42
+ service_api.containers
43
+ end
44
+
45
+ def update_image(version)
46
+ docker_cloud_api.services.update(uuid, image: "#{image_name}:#{version}")
47
+ rescue RestClient::InternalServerError
48
+ raise KumoDockerCloud::ServiceDeployError.new("Something went wrong during service update on Docker Cloud's end")
49
+ end
50
+
51
+ def redeploy
52
+ docker_cloud_api.services.redeploy(uuid)
53
+ rescue RestClient::InternalServerError
54
+ raise KumoDockerCloud::ServiceDeployError.new("Something went wrong during service update on Docker Cloud's end")
55
+ end
56
+
57
+ def docker_cloud_api
58
+ @docker_cloud_api ||= KumoDockerCloud::DockerCloudApi.new
59
+ end
60
+
61
+ def service_api
62
+ docker_cloud_api.service_by_stack_and_service_name(stack_name, name)
63
+ end
64
+
65
+ def uuid
66
+ service_api.uuid
67
+ end
68
+
69
+ def image_name
70
+ service_api.image_name.split(':').first
71
+ end
72
+ end
73
+ end
@@ -8,39 +8,20 @@ module KumoDockerCloud
8
8
  @options = options
9
9
  end
10
10
 
11
- def deploy(version)
12
- update_image(version)
13
- redeploy
14
- validate_deployment(version)
15
- end
16
-
17
- private
18
-
19
- def update_image(version)
20
- docker_cloud_api.services.update(service_uuid, image: "redbubble/#{app_name}:#{version}")
21
- end
11
+ def deploy(service_name, version, checks = nil, check_timeout = 300)
12
+ validate_params(service_name, 'Service name')
13
+ validate_params(version, 'Version')
22
14
 
23
- def redeploy
24
- docker_cloud_api.services.redeploy(service_uuid)
15
+ service = Service.new(stack_name, service_name)
16
+ service.deploy(version)
17
+ service.check(checks, check_timeout) if checks
25
18
  end
26
19
 
27
- def validate_deployment(version)
28
- deployment = Deployment.new(stack_name, version)
29
- deployment.app_name = app_name
30
- deployment.contactable = options[:contactable]
31
- deployment.validate
32
- end
33
-
34
- def service_uuid
35
- @service_uuid ||= begin
36
- services = docker_cloud_api.services_by_stack_name(stack_name)
37
- services.first.uuid
38
- end
39
- end
20
+ private
40
21
 
41
- def docker_cloud_api
42
- @docker_cloud_api ||= KumoDockerCloud::DockerCloudApi.new
22
+ def validate_params(param_value, param_name)
23
+ raise KumoDockerCloud::Error.new("#{param_name} cannot be nil") unless param_value
24
+ raise KumoDockerCloud::Error.new("#{param_name} cannot be empty") if param_value.empty?
43
25
  end
44
-
45
26
  end
46
27
  end
@@ -37,6 +37,35 @@ module KumoDockerCloud
37
37
  end
38
38
  end
39
39
 
40
+ def wait_for_exit_state(time_limit)
41
+ start_time = Time.now
42
+
43
+ while Time.now.to_i - start_time.to_i < time_limit
44
+ @stateful = state_provider.call
45
+
46
+ print "."
47
+
48
+ unless current_exit_code.nil?
49
+ break
50
+ end
51
+
52
+ sleep(1)
53
+ end
54
+
55
+ print "\n"
56
+
57
+ if current_exit_code.nil?
58
+ puts "#{@stateful[:name]} deployment timed out after #{time_limit} seconds"
59
+ raise TimeoutError.new
60
+ end
61
+
62
+ if current_exit_code != 0
63
+ error_message = "#{@stateful[:name]} deployment failed with exit code #{current_exit_code}"
64
+ puts error_message
65
+ raise error_message
66
+ end
67
+ end
68
+
40
69
  private
41
70
 
42
71
  def current_state
@@ -44,6 +73,11 @@ module KumoDockerCloud
44
73
  @stateful.fetch(:state, 'an unknown state')
45
74
  end
46
75
 
76
+ def current_exit_code
77
+ return 'an unknown state' if @stateful.nil?
78
+ @stateful.fetch(:exit_code, nil)
79
+ end
80
+
47
81
  end
48
82
 
49
83
  end
@@ -1,3 +1,3 @@
1
1
  module KumoDockerCloud
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'httpi'
3
+
4
+ describe KumoDockerCloud::Service do
5
+ let(:service_image) { "repository/docker_image_name:version" }
6
+ let(:service_uuid) { "i_am_a_unique_snowflower" }
7
+ let(:docker_cloud_service_api) { double(:services_api, uuid: service_uuid, image_name: service_image, containers: []) }
8
+ let(:docker_cloud_api) { instance_double(KumoDockerCloud::DockerCloudApi, services: docker_cloud_service_api)}
9
+
10
+ subject { described_class.new('stack_name', 'service_name') }
11
+
12
+ before do
13
+ allow(KumoDockerCloud::DockerCloudApi).to receive(:new).and_return(docker_cloud_api)
14
+
15
+ allow(docker_cloud_api).to receive(:service_by_stack_and_service_name).with('stack_name', 'service_name').and_return(docker_cloud_service_api)
16
+ end
17
+
18
+ describe '#deploy' do
19
+ it 'runs the actual update and redeploy methods' do
20
+ expect(docker_cloud_service_api).to receive(:update).with(service_uuid, { image: service_image })
21
+ expect(docker_cloud_service_api).to receive(:redeploy).with(service_uuid)
22
+ subject.deploy('version')
23
+ end
24
+
25
+ it 'raises an appropriate exception when there is an error during image update' do
26
+ expect(docker_cloud_service_api).to receive(:update).and_raise(RestClient::InternalServerError)
27
+ expect { subject.deploy('version') }.to raise_error(KumoDockerCloud::ServiceDeployError, "Something went wrong during service update on Docker Cloud's end")
28
+ end
29
+
30
+ it 'raises an appropriate exception when there is an error during redployment' do
31
+ allow(docker_cloud_service_api).to receive(:update).with(service_uuid, { image: service_image })
32
+ expect(docker_cloud_service_api).to receive(:redeploy).and_raise(RestClient::InternalServerError)
33
+ expect { subject.deploy('version') }.to raise_error(KumoDockerCloud::ServiceDeployError, "Something went wrong during service update on Docker Cloud's end")
34
+ end
35
+ end
36
+
37
+ describe '#check' do
38
+ let(:http_lib) { double('http_lib') }
39
+ let(:container_status_check) { lambda { |container| container.state == 'Running' } }
40
+ let(:endpoint_check) do
41
+ lambda do |container|
42
+ url = "#{container.container_ports.first[:endpoint_uri]}/site_status"
43
+ response = http_lib.get(url)
44
+ response == 200
45
+ end
46
+ end
47
+ let(:checks) {[container_status_check, endpoint_check]}
48
+ let(:check_timeout) { 300 }
49
+ let(:whale1) { double(:whale1, container_ports: [{endpoint_uri: "http://whale1.test"}], reload: nil) }
50
+ let(:whale2) { double(:whale2, state: "Running", container_ports: [{endpoint_uri: "http://whale2.test"}], reload: nil)}
51
+ let(:containers) { [whale1, whale2] }
52
+
53
+ before do
54
+ allow(http_lib).to receive(:get).with("http://whale1.test/site_status").and_return(200)
55
+ allow(http_lib).to receive(:get).with("http://whale2.test/site_status").and_return("timeout", "timeout", 200)
56
+ allow(whale1).to receive(:state).and_return("Starting", "Running")
57
+ allow(docker_cloud_service_api).to receive(:containers).and_return(containers)
58
+ end
59
+
60
+ it 'resolves to true if all the checks eventually pass' do
61
+ allow(subject).to receive(:sleep).and_return(nil)
62
+ expect(subject.check(checks, check_timeout)).to eq(true)
63
+ end
64
+
65
+
66
+ it 'raises an error if any check fails to pass within the timeout period' do
67
+ short_timeout = 2
68
+ allow(whale1).to receive(:state).and_return("Starting")
69
+ expect { subject.check(checks, short_timeout) }.to raise_error(KumoDockerCloud::ServiceDeployError, "One or more checks failed to pass within the timeout")
70
+ end
71
+ end
72
+ end
@@ -1,29 +1,63 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe KumoDockerCloud::Stack do
4
+ let(:service_api) { instance_double(DockerCloud::ServiceAPI) }
5
+ let(:uuid) { 'foo' }
6
+ let(:service_name) { 'test_service' }
7
+ let(:app_name) { 'test_app' }
8
+ 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
22
+
4
23
  describe '#deploy' do
5
- subject { described_class.new(app_name, environment_name).deploy(app_version) }
6
-
7
- let(:app_name) { 'test_app' }
8
- let(:environment_name) { 'environment' }
9
- let(:app_version) { '1' }
10
- let(:uuid) { 'foo' }
11
- let(:client) { instance_double(DockerCloud::Client, stacks: stacks, services: service_api) }
12
- let(:service_api) { instance_double(DockerCloud::ServiceAPI) }
13
- let(:stacks) { double('stacks', all: [stack]) }
14
- let(:stack) { instance_double(DockerCloud::Stack, name: "#{app_name}-#{environment_name}", services: [service]) }
15
- let(:service) { instance_double(DockerCloud::Service, uuid: uuid, containers: []) }
16
- let(:state_validator) { instance_double(KumoDockerCloud::StateValidator, wait_for_state: nil) }
17
-
18
- before do
19
- allow(::DockerCloud::Client).to receive(:new).and_return(client)
20
- allow(KumoDockerCloud::StateValidator).to receive(:new).and_return(state_validator)
24
+ subject { described_class.new(app_name, environment_name) }
25
+
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')
28
+ end
29
+
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
33
+
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')
21
36
  end
22
37
 
23
- it 'uses the service api to update the image and redeploy' do
24
- expect(service_api).to receive(:update).with(uuid, {image: "redbubble/#{app_name}:#{app_version}"})
25
- expect(service_api).to receive(:redeploy).with(uuid)
26
- subject
38
+ it 'complains if passed an empty version' do
39
+ expect { subject.deploy("test_service", "") }.to raise_error(KumoDockerCloud::Error, 'Version cannot be empty')
27
40
  end
41
+
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
46
+
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
53
+
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)
60
+ end
61
+
28
62
  end
29
63
  end
@@ -6,7 +6,7 @@ describe KumoDockerCloud::StateValidator do
6
6
  subject { state_validator.wait_for_state('done', 1) }
7
7
  let(:state_validator) { described_class.new(state_provider) }
8
8
  let(:state_provider) { double('state_provider', call: service) }
9
- let(:service) { { state: service_state, name: 'service name' } }
9
+ let(:service) { {state: service_state, name: 'service name'} }
10
10
 
11
11
  context 'the right state' do
12
12
  let(:service_state) { 'done' }
@@ -26,4 +26,36 @@ describe KumoDockerCloud::StateValidator do
26
26
 
27
27
  end
28
28
 
29
+ describe '#wait_for_exit_state' do
30
+ subject { state_validator.wait_for_exit_state(1) }
31
+ let(:state_validator) { described_class.new(state_provider) }
32
+ let(:state_provider) { double('state_provider', call: service) }
33
+ let(:service) { {name: 'service name', exit_code: exit_code} }
34
+
35
+ context 'success' do
36
+ let(:exit_code) { 0 }
37
+
38
+ it 'succeeds immediately if the state is right' do
39
+ subject
40
+ end
41
+ end
42
+
43
+ context 'failure' do
44
+ let(:exit_code) { 1 }
45
+
46
+ it 'raises an exception' do
47
+ expect { subject }.to raise_error("service name deployment failed with exit code 1")
48
+ end
49
+ end
50
+
51
+ context 'no exit before the timeout' do
52
+ let(:exit_code) { nil }
53
+
54
+ it 'raises an exception' do
55
+ expect { subject }.to raise_error(Timeout::Error)
56
+ end
57
+ end
58
+
59
+ end
60
+
29
61
  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: 1.0.0
4
+ version: 1.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-05 00:00:00.000000000 Z
13
+ date: 2016-04-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: httpi
@@ -132,6 +132,8 @@ files:
132
132
  - lib/kumo_dockercloud/docker_cloud_api.rb
133
133
  - lib/kumo_dockercloud/environment.rb
134
134
  - lib/kumo_dockercloud/environment_config.rb
135
+ - lib/kumo_dockercloud/errors.rb
136
+ - lib/kumo_dockercloud/service.rb
135
137
  - lib/kumo_dockercloud/stack.rb
136
138
  - lib/kumo_dockercloud/stack_file.rb
137
139
  - lib/kumo_dockercloud/state_validator.rb
@@ -147,6 +149,7 @@ files:
147
149
  - spec/kumo_dockercloud/docker_cloud_api_spec.rb
148
150
  - spec/kumo_dockercloud/environment_config_spec.rb
149
151
  - spec/kumo_dockercloud/environment_spec.rb
152
+ - spec/kumo_dockercloud/service_spec.rb
150
153
  - spec/kumo_dockercloud/stack_file_spec.rb
151
154
  - spec/kumo_dockercloud/stack_spec.rb
152
155
  - spec/kumo_dockercloud/state_validator_spec.rb
@@ -184,6 +187,7 @@ test_files:
184
187
  - spec/kumo_dockercloud/docker_cloud_api_spec.rb
185
188
  - spec/kumo_dockercloud/environment_config_spec.rb
186
189
  - spec/kumo_dockercloud/environment_spec.rb
190
+ - spec/kumo_dockercloud/service_spec.rb
187
191
  - spec/kumo_dockercloud/stack_file_spec.rb
188
192
  - spec/kumo_dockercloud/stack_spec.rb
189
193
  - spec/kumo_dockercloud/state_validator_spec.rb