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