kumo_dockercloud 1.0.0 → 1.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: 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