centurion 1.0.6

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.
@@ -0,0 +1,52 @@
1
+ require 'centurion/docker_registry'
2
+
3
+ task :list do
4
+ invoke 'list:tags'
5
+ invoke 'list:running_containers'
6
+ end
7
+
8
+ namespace :list do
9
+ task :running_container_tags do
10
+
11
+ tags = get_current_tags_for(fetch(:image))
12
+
13
+ $stderr.puts "\n\nCurrent #{current_environment} tags for #{fetch(:image)}:\n\n"
14
+ tags.each do |info|
15
+ if info && !info[:tags].empty?
16
+ $stderr.puts "#{'%-20s' % info[:server]}: #{info[:tags].join(', ')}"
17
+ else
18
+ $stderr.puts "#{'%-20s' % info[:server]}: NO TAGS!"
19
+ end
20
+ end
21
+
22
+ $stderr.puts "\nAll tags for this image: #{tags.map { |t| t[:tags] }.flatten.uniq.join(', ')}"
23
+ end
24
+
25
+ task :tags do
26
+ begin
27
+ registry = Centurion::DockerRegistry.new()
28
+ tags = registry.respository_tags(fetch(:image))
29
+ tags.each do |tag|
30
+ puts "\t#{tag[0]}\t-> #{tag[1][0..11]}"
31
+ end
32
+ rescue StandardError => e
33
+ error "Couldn't communicate with Registry: #{e.message}"
34
+ end
35
+ puts
36
+ end
37
+
38
+ task :running_containers do
39
+ on_each_docker_host do |target_server|
40
+ begin
41
+ running_containers = target_server.ps
42
+ running_containers.each do |container|
43
+ puts container.inspect
44
+ end
45
+ rescue StandardError => e
46
+ error "Couldn't communicate with Docker on #{target_server.hostname}: #{e.message}"
47
+ raise
48
+ end
49
+ puts
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'capistrano_dsl'
3
+
4
+ class DSLTest
5
+ extend Capistrano::DSL
6
+ end
7
+
8
+ describe Capistrano::DSL do
9
+ before do
10
+ DSLTest.clear_env
11
+ end
12
+
13
+ context 'handling multiple environments' do
14
+ it 'sets the environment' do
15
+ expect { DSLTest.set_current_environment(:test) }.not_to raise_error
16
+ end
17
+
18
+ it 'fetchs the current environment' do
19
+ DSLTest.set_current_environment(:test)
20
+ expect(DSLTest.current_environment).to eq(:test)
21
+ end
22
+ end
23
+
24
+ context 'without a current environment set' do
25
+ it 'dies if the current_environment is not set' do
26
+ expect { DSLTest.set(:foo, 'asdf') }.to raise_error(Capistrano::DSL::CurrentEnvironmentNotSetError)
27
+ end
28
+ end
29
+
30
+ context 'with a current environment set' do
31
+ before do
32
+ DSLTest.set_current_environment(:test)
33
+ end
34
+
35
+ it 'stores variables in the environment' do
36
+ expect { DSLTest.set(:foo, 'bar') }.not_to raise_error
37
+ expect(DSLTest).to have_key_and_value(:foo, 'bar')
38
+ end
39
+
40
+ it 'deletes keys from the environment' do
41
+ DSLTest.set(:foo, 'bar')
42
+ expect(DSLTest).to have_key_and_value(:foo, 'bar')
43
+ DSLTest.delete(:foo)
44
+ expect(DSLTest.fetch(:foo)).to be_nil
45
+ end
46
+
47
+ it 'returns true for any? when the value exists' do
48
+ DSLTest.set(:foo, 'bar')
49
+ expect(DSLTest.any?(:foo)).to be_true
50
+ end
51
+
52
+ it 'returns false for any? when the value does not exist' do
53
+ expect(DSLTest.any?(:foo)).to be_false
54
+ end
55
+
56
+ it 'passes through the any? method to values that support it' do
57
+ class NoAny
58
+ def any?
59
+ 'oh no'
60
+ end
61
+ end
62
+
63
+ DSLTest.set(:foo, NoAny.new)
64
+ DSLTest.any?(:foo).should eq('oh no')
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+ require 'centurion/deploy_dsl'
3
+ require 'capistrano_dsl'
4
+
5
+ class DeployDSLTest
6
+ extend Capistrano::DSL
7
+ extend Centurion::DeployDSL
8
+ end
9
+
10
+ describe Centurion::DeployDSL do
11
+ before do
12
+ DeployDSLTest.clear_env
13
+ DeployDSLTest.set_current_environment('test')
14
+ end
15
+
16
+ it 'exposes an easy wrapper for handling each Docker host' do
17
+ recipient = double('recipient')
18
+ expect(recipient).to receive(:ping).with('host1')
19
+ expect(recipient).to receive(:ping).with('host2')
20
+
21
+ DeployDSLTest.set(:hosts, %w{ host1 host2 })
22
+ DeployDSLTest.on_each_docker_host { |h| recipient.ping(h.hostname) }
23
+ end
24
+
25
+ it 'adds new env_vars to the existing ones' do
26
+ DeployDSLTest.set(:env_vars, { 'SHAKESPEARE' => 'Hamlet' })
27
+ DeployDSLTest.env_vars('DICKENS' => 'David Copperfield')
28
+
29
+ expect(DeployDSLTest.fetch(:env_vars)).to include(
30
+ 'SHAKESPEARE' => 'Hamlet',
31
+ 'DICKENS' => 'David Copperfield'
32
+ )
33
+ end
34
+
35
+ it 'adds hosts to the host list' do
36
+ DeployDSLTest.set(:hosts, [ 'host1' ])
37
+ DeployDSLTest.host('host2')
38
+
39
+ expect(DeployDSLTest).to have_key_and_value(:hosts, %w{ host1 host2 })
40
+ end
41
+
42
+ describe '#localhost' do
43
+ it 'adds a host by reading DOCKER_HOST if present' do
44
+ expect(ENV).to receive(:[]).with('DOCKER_HOST').and_return('tcp://127.1.1.1:4240')
45
+ DeployDSLTest.localhost
46
+ expect(DeployDSLTest).to have_key_and_value(:hosts, %w[ 127.1.1.1:4240 ])
47
+ end
48
+
49
+ it 'adds a host defaulting to loopback if DOCKER_HOST is not present' do
50
+ expect(ENV).to receive(:[]).with('DOCKER_HOST').and_return(nil)
51
+ DeployDSLTest.localhost
52
+ expect(DeployDSLTest).to have_key_and_value(:hosts, %w[ 127.0.0.1 ])
53
+ end
54
+ end
55
+
56
+ describe '#host_port' do
57
+ it 'raises unless passed container_port in the options' do
58
+ expect { DeployDSLTest.host_port(666, {}) }.to raise_error(ArgumentError, /:container_port/)
59
+ end
60
+
61
+ it 'adds new bind ports to the list' do
62
+ dummy_value = { '666/tcp' => ['value'] }
63
+ DeployDSLTest.set(:port_bindings, dummy_value)
64
+ DeployDSLTest.host_port(999, container_port: 80)
65
+
66
+ expect(DeployDSLTest).to have_key_and_value(
67
+ :port_bindings,
68
+ dummy_value.merge('80/tcp' => [{ 'HostIp' => '0.0.0.0', 'HostPort' => '999' }])
69
+ )
70
+ end
71
+
72
+ it 'does not explode if port_bindings is empty' do
73
+ expect { DeployDSLTest.host_port(999, container_port: 80) }.not_to raise_error
74
+ end
75
+
76
+ it 'raises if invalid options are passed' do
77
+ expect { DeployDSLTest.host_port(80, asdf: 'foo') }.to raise_error(ArgumentError, /invalid key!/)
78
+ end
79
+ end
80
+
81
+ describe '#host_volume' do
82
+ it 'raises unless passed the container_volume option' do
83
+ expect { DeployDSLTest.host_volume('foo', {}) }.to raise_error(ArgumentError, /:container_volume/)
84
+ end
85
+
86
+ it 'raises when passed bogus options' do
87
+ expect { DeployDSLTest.host_volume('foo', bogus: 1) }.to raise_error(ArgumentError, /invalid key!/)
88
+ end
89
+
90
+ it 'adds new host volumes' do
91
+ expect(DeployDSLTest.fetch(:binds)).to be_nil
92
+ DeployDSLTest.host_volume('volume1', container_volume: '/dev/sdd')
93
+ DeployDSLTest.host_volume('volume2', container_volume: '/dev/sde')
94
+ expect(DeployDSLTest.fetch(:binds)).to eq %w{ volume1:/dev/sdd volume2:/dev/sde }
95
+ end
96
+ end
97
+
98
+ it 'gets current tags for an image' do
99
+ Centurion::DockerServer.any_instance.stub(current_tags_for: [ 'foo' ])
100
+ DeployDSLTest.set(:hosts, [ 'host1' ])
101
+
102
+ expect(DeployDSLTest.get_current_tags_for('asdf')).to eq [ { server: 'host1', tags: [ 'foo'] } ]
103
+ end
104
+ end
@@ -0,0 +1,220 @@
1
+ require 'centurion/deploy'
2
+ require 'centurion/deploy_dsl'
3
+ require 'centurion/logging'
4
+
5
+ describe Centurion::Deploy do
6
+ let(:mock_ok_status) { double('http_status_ok').tap { |s| s.stub(status: 200) } }
7
+ let(:mock_bad_status) { double('http_status_ok').tap { |s| s.stub(status: 500) } }
8
+ let(:server) { double('docker_server').tap { |s| s.stub(hostname: 'host1'); s.stub(:attach) } }
9
+ let(:port) { 8484 }
10
+ let(:container) { { 'Ports' => [{ 'PublicPort' => port }, 'Created' => Time.now.to_i ], 'Id' => '21adfd2ef2ef2349494a', 'Names' => [ 'name1' ] } }
11
+ let(:endpoint) { '/status/check' }
12
+ let(:test_deploy) do
13
+ Object.new.tap do |o|
14
+ o.send(:extend, Centurion::Deploy)
15
+ o.send(:extend, Centurion::DeployDSL)
16
+ o.send(:extend, Centurion::Logging)
17
+ end
18
+ end
19
+
20
+ describe '#http_status_ok?' do
21
+ it 'validates HTTP status checks when the response is good' do
22
+ expect(Excon).to receive(:get).and_return(mock_ok_status)
23
+ expect(test_deploy.http_status_ok?(server, port, endpoint)).to be_true
24
+ end
25
+
26
+ it 'identifies bad HTTP responses' do
27
+ expect(Excon).to receive(:get).and_return(mock_bad_status)
28
+ test_deploy.stub(:warn)
29
+ expect(test_deploy.http_status_ok?(server, port, endpoint)).to be_false
30
+ end
31
+
32
+ it 'outputs the HTTP status when it is not OK' do
33
+ expect(Excon).to receive(:get).and_return(mock_bad_status)
34
+ expect(test_deploy).to receive(:warn).with(/Got HTTP status: 500/)
35
+ expect(test_deploy.http_status_ok?(server, port, endpoint)).to be_false
36
+ end
37
+
38
+ it 'handles SocketErrors and outputs a message' do
39
+ expect(Excon).to receive(:get).and_raise(Excon::Errors::SocketError.new(RuntimeError.new()))
40
+ expect(test_deploy).to receive(:warn).with(/Failed to connect/)
41
+ expect(test_deploy.http_status_ok?(server, port, endpoint)).to be_false
42
+ end
43
+ end
44
+
45
+ describe '#container_up?' do
46
+ it 'recognizes when no containers are running' do
47
+ expect(server).to receive(:find_containers_by_public_port).and_return([])
48
+
49
+ test_deploy.container_up?(server, port).should be_false
50
+ end
51
+
52
+ it 'complains when more than one container is bound to this port' do
53
+ expect(server).to receive(:find_containers_by_public_port).and_return([1,2])
54
+ expect(test_deploy).to receive(:error).with /More than one container/
55
+
56
+ test_deploy.container_up?(server, port).should be_false
57
+ end
58
+
59
+ it 'recognizes when the container is actually running' do
60
+ expect(server).to receive(:find_containers_by_public_port).and_return([container])
61
+ expect(test_deploy).to receive(:info).with /Found container/
62
+
63
+ test_deploy.container_up?(server, port).should be_true
64
+ end
65
+ end
66
+
67
+ describe '#wait_for_http_status_ok?' do
68
+ before do
69
+ test_deploy.stub(:info)
70
+ end
71
+
72
+ it 'identifies that a container is up' do
73
+ test_deploy.stub(:container_up? => true)
74
+ test_deploy.stub(:http_status_ok? => true)
75
+
76
+ test_deploy.wait_for_http_status_ok(server, port, '/foo', 'image_id', 'chaucer')
77
+ expect(test_deploy).to have_received(:info).with(/Waiting for the port/)
78
+ expect(test_deploy).to have_received(:info).with('Container is up!')
79
+ end
80
+
81
+ it 'waits when the container is not yet up' do
82
+ test_deploy.stub(:container_up? => false)
83
+ test_deploy.stub(:error)
84
+ test_deploy.stub(:warn)
85
+ expect(test_deploy).to receive(:exit)
86
+ expect(test_deploy).to receive(:sleep).with(0)
87
+
88
+ test_deploy.wait_for_http_status_ok(server, port, '/foo', 'image_id', 'chaucer', 0, 1)
89
+ expect(test_deploy).to have_received(:info).with(/Waiting for the port/)
90
+ end
91
+
92
+ it 'waits when the HTTP status is not OK' do
93
+ test_deploy.stub(:container_up? => true)
94
+ test_deploy.stub(:http_status_ok? => false)
95
+ test_deploy.stub(:error)
96
+ test_deploy.stub(:warn)
97
+ expect(test_deploy).to receive(:exit)
98
+
99
+ test_deploy.wait_for_http_status_ok(server, port, '/foo', 'image_id', 'chaucer', 1, 0)
100
+ expect(test_deploy).to have_received(:info).with(/Waiting for the port/)
101
+ end
102
+ end
103
+
104
+ describe '#cleanup_containers' do
105
+ it 'deletes all but two containers' do
106
+ expect(server).to receive(:old_containers_for_port).with(port.to_s).and_return([
107
+ {'Id' => '123', 'Names' => ['foo']},
108
+ {'Id' => '456', 'Names' => ['foo']},
109
+ {'Id' => '789', 'Names' => ['foo']},
110
+ {'Id' => '0ab', 'Names' => ['foo']},
111
+ {'Id' => 'cde', 'Names' => ['foo']},
112
+ ])
113
+ expect(server).to receive(:remove_container).with('789')
114
+ expect(server).to receive(:remove_container).with('0ab')
115
+ expect(server).to receive(:remove_container).with('cde')
116
+
117
+ test_deploy.cleanup_containers(server, {'80/tcp' => [{'HostIp' => '0.0.0.0', 'HostPort' => port.to_s}]})
118
+ end
119
+ end
120
+
121
+ describe '#stop_containers' do
122
+ it 'calls stop_container on the right containers' do
123
+ second_container = container.dup
124
+ containers = [ container, second_container ]
125
+ bindings = {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]}
126
+
127
+ expect(server).to receive(:find_containers_by_public_port).and_return(containers)
128
+ expect(test_deploy).to receive(:public_port_for).with(bindings).and_return('80')
129
+ expect(server).to receive(:stop_container).with(container['Id']).once
130
+ expect(server).to receive(:stop_container).with(second_container['Id']).once
131
+
132
+ test_deploy.stop_containers(server, bindings)
133
+ end
134
+ end
135
+
136
+ describe '#wait_for_load_balancer_check_interval' do
137
+ it 'knows how long to sleep' do
138
+ timing = double(timing)
139
+ expect(test_deploy).to receive(:fetch).with(:rolling_deploy_check_interval, 5).and_return(timing)
140
+ expect(test_deploy).to receive(:sleep).with(timing)
141
+
142
+ test_deploy.wait_for_load_balancer_check_interval
143
+ end
144
+ end
145
+
146
+ describe '#container_config_for' do
147
+ it 'works with env_vars provided' do
148
+ config = test_deploy.container_config_for(server, 'image_id', {}, 'FOO' => 'BAR')
149
+
150
+ expect(config).to be_a(Hash)
151
+ expect(config.keys).to match_array(%w{ Hostname Image Env ExposedPorts })
152
+ expect(config['Env']).to eq(['FOO=BAR'])
153
+ end
154
+
155
+ it 'works without env_vars or port_bindings' do
156
+ config = test_deploy.container_config_for(server, 'image_id')
157
+
158
+ expect(config).to be_a(Hash)
159
+ expect(config.keys).to match_array(%w{ Hostname Image })
160
+ end
161
+ end
162
+
163
+ describe '#start_new_container' do
164
+ let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
165
+
166
+ it 'configures the container' do
167
+ expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, nil).once
168
+ test_deploy.stub(:start_container_with_config)
169
+
170
+ test_deploy.start_new_container(server, 'image_id', bindings, {})
171
+ end
172
+
173
+ it 'starts the container' do
174
+ expect(test_deploy).to receive(:start_container_with_config).with(server, {}, anything(), anything())
175
+
176
+ test_deploy.start_new_container(server, 'image_id', bindings, {})
177
+ end
178
+
179
+ it 'ultimately asks the server object to do the work' do
180
+ server.should_receive(:create_container).with(
181
+ hash_including({'Image'=>'image_id', 'Hostname'=>'host1', 'ExposedPorts'=>{'80/tcp'=>{}}})
182
+ ).and_return(container)
183
+
184
+ server.should_receive(:start_container)
185
+ server.should_receive(:inspect_container)
186
+
187
+ new_container = test_deploy.start_new_container(server, 'image_id', bindings, {})
188
+ expect(new_container).to eq(container)
189
+ end
190
+ end
191
+
192
+ describe '#launch_console' do
193
+ let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
194
+
195
+ it 'configures the container' do
196
+ expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, nil).once
197
+ test_deploy.stub(:start_container_with_config)
198
+
199
+ test_deploy.start_new_container(server, 'image_id', bindings, {})
200
+ end
201
+
202
+ it 'augments the container_config' do
203
+ expect(test_deploy).to receive(:start_container_with_config).with(server, {},
204
+ anything(),
205
+ hash_including('Cmd' => [ '/bin/bash' ], 'AttachStdin' => true , 'Tty' => true , 'OpenStdin' => true)
206
+ ).and_return({'Id' => 'shakespeare'})
207
+
208
+ test_deploy.launch_console(server, 'image_id', bindings, {})
209
+ end
210
+
211
+ it 'starts the console' do
212
+ expect(test_deploy).to receive(:start_container_with_config).with(
213
+ server, {}, anything(), anything()
214
+ ).and_return({'Id' => 'shakespeare'})
215
+
216
+ test_deploy.launch_console(server, 'image_id', bindings, {})
217
+ expect(server).to have_received(:attach).with('shakespeare')
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'centurion/docker_server'
3
+ require 'centurion/docker_server_group'
4
+
5
+ describe Centurion::DockerServerGroup do
6
+ let(:docker_path) { 'docker' }
7
+ let(:group) { Centurion::DockerServerGroup.new(['host1', 'host2'], docker_path) }
8
+
9
+ it 'takes a hostlist and instantiates DockerServers' do
10
+ expect(group.hosts).to have(2).items
11
+ expect(group.hosts.first).to be_a(Centurion::DockerServer)
12
+ expect(group.hosts.last).to be_a(Centurion::DockerServer)
13
+ end
14
+
15
+ it 'implements Enumerable' do
16
+ expect(group.methods).to be_a_kind_of(Enumerable)
17
+ end
18
+
19
+ it 'prints a friendly message to stderr when iterating' do
20
+ expect(group).to receive(:info).with(/Connecting to Docker on host[0-9]/).twice
21
+
22
+ group.each { |host| }
23
+ end
24
+
25
+ it 'can run parallel operations' do
26
+ item = double('item').tap { |i| i.stub(:dummy_method) }
27
+ expect(item).to receive(:dummy_method).twice
28
+
29
+ expect { group.each_in_parallel { |host| item.dummy_method } }.not_to raise_error
30
+ end
31
+ end