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 +4 -4
- data/lib/kumo_dockercloud.rb +2 -0
- data/lib/kumo_dockercloud/deployment.rb +12 -3
- data/lib/kumo_dockercloud/docker_cloud_api.rb +7 -1
- data/lib/kumo_dockercloud/environment.rb +1 -0
- data/lib/kumo_dockercloud/errors.rb +4 -0
- data/lib/kumo_dockercloud/service.rb +73 -0
- data/lib/kumo_dockercloud/stack.rb +10 -29
- data/lib/kumo_dockercloud/state_validator.rb +34 -0
- data/lib/kumo_dockercloud/version.rb +1 -1
- data/spec/kumo_dockercloud/service_spec.rb +72 -0
- data/spec/kumo_dockercloud/stack_spec.rb +54 -20
- data/spec/kumo_dockercloud/state_validator_spec.rb +33 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c1c50a0d06325de8c9767de8b5e85a4d09dd56d
|
4
|
+
data.tar.gz: b94cfc7050e86e202425c77f8e7bc492db9b5a9b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 317f70cb98f1dfc376cbd258a7a7a55ae6c64cba9b9f0a15e50d146d0c1f12f154f64caecb6c38193cea049aed18be586ec6e2ef0f8a9b46a8be18458d8d26eb
|
7
|
+
data.tar.gz: fc7f5ea565530532030617f44306afe62aa4da2cee3bbb369c2d882a4d076fda5b1bf1e3810544ed8b308ca3f085daab6b9c99f6265b4cb005037300858f94e5
|
data/lib/kumo_dockercloud.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
@@ -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
|
-
|
13
|
-
|
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
|
-
|
24
|
-
|
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
|
-
|
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
|
42
|
-
|
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
|
@@ -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)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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 '
|
24
|
-
expect(
|
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) { {
|
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.
|
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-
|
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
|