centurion 1.6.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module Centurion
2
- VERSION = '1.6.0'
2
+ VERSION = '1.8.0'
3
3
  end
@@ -1,4 +1,4 @@
1
- # Copied from ActiveSupport 4.2.1 lib/active_support/core_ext/numeric/bytes.rb
1
+ # Modified from ActiveSupport 4.2.1 lib/active_support/core_ext/numeric/bytes.rb
2
2
  #
3
3
  # NOTE that THIS LICENSE ONLY APPLIES TO THIS FILE itself, not
4
4
  # to the rest of the project.
@@ -25,68 +25,70 @@
25
25
  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26
26
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27
27
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+ unless Numeric.constants.include?(:KILOBYTE)
29
+ class Numeric
30
+ KILOBYTE = 1024 unless defined? KILOBYTE
31
+ MEGABYTE = KILOBYTE * 1024 unless defined? MEGABYTE
32
+ GIGABYTE = MEGABYTE * 1024 unless defined? GIGABYTE
33
+ TERABYTE = GIGABYTE * 1024 unless defined? TERABYTE
34
+ PETABYTE = TERABYTE * 1024 unless defined? PETABYTE
35
+ EXABYTE = PETABYTE * 1024 unless defined? EXABYTE
28
36
 
29
- class Numeric
30
- KILOBYTE = 1024
31
- MEGABYTE = KILOBYTE * 1024
32
- GIGABYTE = MEGABYTE * 1024
33
- TERABYTE = GIGABYTE * 1024
34
- PETABYTE = TERABYTE * 1024
35
- EXABYTE = PETABYTE * 1024
37
+ # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
38
+ #
39
+ # 2.bytes # => 2
40
+ def bytes
41
+ self
42
+ end unless method_defined? :bytes
43
+ alias :byte :bytes unless method_defined? :byte
36
44
 
37
- # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
38
- #
39
- # 2.bytes # => 2
40
- def bytes
41
- self
42
- end
43
- alias :byte :bytes
44
-
45
- # Returns the number of bytes equivalent to the kilobytes provided.
46
- #
47
- # 2.kilobytes # => 2048
48
- def kilobytes
49
- self * KILOBYTE
50
- end
51
- alias :kilobyte :kilobytes
45
+ # Returns the number of bytes equivalent to the kilobytes provided.
46
+ #
47
+ # 2.kilobytes # => 2048
48
+ def kilobytes
49
+ self * KILOBYTE
50
+ end unless method_defined? :kilobytes
51
+ alias :kilobyte :kilobytes unless method_defined? :kilobyte
52
52
 
53
- # Returns the number of bytes equivalent to the megabytes provided.
54
- #
55
- # 2.megabytes # => 2_097_152
56
- def megabytes
57
- self * MEGABYTE
58
- end
59
- alias :megabyte :megabytes
53
+ # Returns the number of bytes equivalent to the megabytes provided.
54
+ #
55
+ # 2.megabytes # => 2_097_152
56
+ def megabytes
57
+ self * MEGABYTE
58
+ end unless method_defined? :megabytes?
59
+ alias :megabyte :megabytes unless method_defined? :megabyte
60
60
 
61
- # Returns the number of bytes equivalent to the gigabytes provided.
62
- #
63
- # 2.gigabytes # => 2_147_483_648
64
- def gigabytes
65
- self * GIGABYTE
66
- end
67
- alias :gigabyte :gigabytes
61
+ # Returns the number of bytes equivalent to the gigabytes provided.
62
+ #
63
+ # 2.gigabytes # => 2_147_483_648
64
+ def gigabytes
65
+ self * GIGABYTE
66
+ end unless method_defined? :gigabytes
67
+ alias :gigabyte :gigabytes unless method_defined? :gigabyte
68
68
 
69
- # Returns the number of bytes equivalent to the terabytes provided.
70
- #
71
- # 2.terabytes # => 2_199_023_255_552
72
- def terabytes
73
- self * TERABYTE
74
- end
75
- alias :terabyte :terabytes
69
+ # Returns the number of bytes equivalent to the terabytes provided.
70
+ #
71
+ # 2.terabytes # => 2_199_023_255_552
72
+ def terabytes
73
+ self * TERABYTE
74
+ end unless method_defined? :terabytes
75
+ alias :terabyte :terabytes unless method_defined? :terabyte
76
76
 
77
- # Returns the number of bytes equivalent to the petabytes provided.
78
- #
79
- # 2.petabytes # => 2_251_799_813_685_248
80
- def petabytes
81
- self * PETABYTE
82
- end
83
- alias :petabyte :petabytes
77
+ # Returns the number of bytes equivalent to the petabytes provided.
78
+ #
79
+ # 2.petabytes # => 2_251_799_813_685_248
80
+ def petabytes
81
+ self * PETABYTE
82
+ end unless method_defined? :petabytes
83
+ alias :petabyte :petabytes unless method_defined? :petabyte
84
84
 
85
- # Returns the number of bytes equivalent to the exabytes provided.
86
- #
87
- # 2.exabytes # => 2_305_843_009_213_693_952
88
- def exabytes
89
- self * EXABYTE
85
+ # Returns the number of bytes equivalent to the exabytes provided.
86
+ #
87
+ # 2.exabytes # => 2_305_843_009_213_693_952
88
+ def exabytes
89
+ self * EXABYTE
90
+ end unless method_defined? :exabytes
91
+ alias :exabyte :exabytes unless method_defined? :exabyte
90
92
  end
91
- alias :exabyte :exabytes
92
93
  end
94
+
@@ -7,6 +7,9 @@ namespace :centurion do
7
7
  task :clean_environment do
8
8
  ENV.delete('DOCKER_HOST')
9
9
  ENV.delete('DOCKER_TLS_VERIFY')
10
+ # Preserve original DOCKER_CERT_PATH for use by dogestry.
11
+ # See also Centurion::Dogestry#set_envs
12
+ set(:original_docker_cert_path, ENV['DOCKER_CERT_PATH'])
10
13
  ENV.delete('DOCKER_CERT_PATH')
11
14
  end
12
15
  end
@@ -25,6 +25,18 @@ end
25
25
 
26
26
  task :stop => ['deploy:stop']
27
27
 
28
+ namespace :dev do
29
+ task :export_only do
30
+ # This removes the known-to-be-problematic bundler env
31
+ # vars but doesn't try to sanitize the whole ENV. Doing
32
+ # so breaks things like rbenv which we need to use when
33
+ # testing. A /bin/bash -l will help further clean it up.
34
+ ENV.reject! { |(var, val)| var =~ /^BUNDLE_/ }
35
+ fetch(:env_vars).each { |(var, value)| ENV[var] = value }
36
+ exec fetch(:development_shell, '/bin/bash -l')
37
+ end
38
+ end
39
+
28
40
  namespace :deploy do
29
41
  include Centurion::Deploy
30
42
 
@@ -47,6 +59,10 @@ namespace :deploy do
47
59
  aws_secret_key: fetch(:aws_secret_key),
48
60
  s3_bucket: fetch(:s3_bucket),
49
61
  s3_region: fetch(:s3_region) || 'us-east-1',
62
+ tlsverify: fetch(:tlsverify),
63
+ tlscacert: fetch(:tlscacert),
64
+ tlscert: fetch(:tlscert),
65
+ original_docker_cert_path: fetch(:original_docker_cert_path)
50
66
  )
51
67
 
52
68
  target_servers = Centurion::DockerServerGroup.new(fetch(:hosts), fetch(:docker_path))
@@ -75,7 +91,7 @@ namespace :deploy do
75
91
  # - remote: stop
76
92
  task :stop do
77
93
  on_each_docker_host do |server|
78
- stop_containers(server, fetch(:port_bindings), fetch(:stop_timeout, 30))
94
+ stop_containers(server, defined_service, fetch(:stop_timeout, 30))
79
95
  end
80
96
  end
81
97
 
@@ -85,59 +101,32 @@ namespace :deploy do
85
101
  # - remote: inspect container
86
102
  task :start_new do
87
103
  on_each_docker_host do |server|
88
- start_new_container(
89
- server,
90
- fetch(:image_id),
91
- fetch(:port_bindings),
92
- fetch(:binds),
93
- fetch(:env_vars),
94
- fetch(:command),
95
- fetch(:memory),
96
- fetch(:cpu_shares)
97
- )
104
+ start_new_container(server, defined_service, defined_restart_policy)
98
105
  end
99
106
  end
100
107
 
101
108
  task :launch_console do
102
109
  on_each_docker_host do |server|
103
- launch_console(
104
- server,
105
- fetch(:image_id),
106
- fetch(:port_bindings),
107
- fetch(:binds),
108
- fetch(:env_vars)
109
- )
110
+ launch_console(server, defined_service)
110
111
  end
111
112
  end
112
113
 
113
114
  task :rolling_deploy do
114
115
  on_each_docker_host do |server|
115
- stop_containers(server, fetch(:port_bindings), fetch(:stop_timeout, 30))
116
-
117
- start_new_container(
118
- server,
119
- fetch(:image_id),
120
- fetch(:port_bindings),
121
- fetch(:binds),
122
- fetch(:env_vars),
123
- fetch(:command),
124
- fetch(:memory),
125
- fetch(:cpu_shares)
126
- )
116
+ service = defined_service
127
117
 
128
- skip_ports = Array(fetch(:rolling_deploy_skip_ports, [])).map(&:to_s)
118
+ stop_containers(server, service, fetch(:stop_timeout, 30))
129
119
 
130
- fetch(:port_bindings).each_pair do |container_port, host_ports|
131
- port = host_ports.first['HostPort']
132
- next if skip_ports.include?(port)
120
+ container = start_new_container(server, service, defined_restart_policy)
133
121
 
122
+ public_ports = service.public_ports - fetch(:rolling_deploy_skip_ports, [])
123
+ public_ports.each do |port|
134
124
  wait_for_health_check_ok(
135
125
  fetch(:health_check, method(:http_status_ok?)),
136
126
  server,
127
+ container['Id'],
137
128
  port,
138
129
  fetch(:status_endpoint, '/'),
139
- fetch(:image),
140
- fetch(:tag),
141
130
  fetch(:rolling_deploy_wait_time, 5),
142
131
  fetch(:rolling_deploy_retries, 24)
143
132
  )
@@ -148,8 +137,8 @@ namespace :deploy do
148
137
  end
149
138
 
150
139
  task :cleanup do
151
- on_each_docker_host do |target_server|
152
- cleanup_containers(target_server, fetch(:port_bindings))
140
+ on_each_docker_host do |server|
141
+ cleanup_containers(server, defined_service)
153
142
  end
154
143
  end
155
144
 
@@ -189,10 +178,7 @@ namespace :deploy do
189
178
  if fetch(:registry) == 'dogestry'
190
179
  invoke 'deploy:dogestry:pull_image'
191
180
  else
192
- hosts, docker_path = fetch(:hosts, []), fetch(:docker_path)
193
- target_servers = Centurion::DockerServerGroup.new(hosts, docker_path,
194
- build_tls_params)
195
- target_servers.each_in_parallel do |target_server|
181
+ build_server_group.each_in_parallel do |target_server|
196
182
  target_server.pull(fetch(:image), fetch(:tag))
197
183
  end
198
184
  end
@@ -250,4 +236,5 @@ namespace :deploy do
250
236
 
251
237
  invoke 'deploy'
252
238
  end
239
+
253
240
  end
@@ -23,23 +23,53 @@ describe Centurion::DeployDSL do
23
23
  end
24
24
 
25
25
  it 'has a DSL method for specifying the start command' do
26
- command = ['/bin/echo', 'hi']
26
+ command = %w{ /bin/echo hi }
27
27
  DeployDSLTest.command command
28
- expect(DeployDSLTest.fetch(:command)).to equal(command)
28
+ expect(DeployDSLTest.defined_service.command).to eq(command)
29
29
  end
30
30
 
31
31
  it 'adds new env_vars to the existing ones, as strings' do
32
- DeployDSLTest.set(:env_vars, { 'SHAKESPEARE' => 'Hamlet' })
32
+ DeployDSLTest.env_vars('SHAKESPEARE' => 'Hamlet')
33
33
  DeployDSLTest.env_vars('DICKENS' => 'David Copperfield',
34
34
  :DICKENS_BIRTH_YEAR => 1812)
35
35
 
36
- expect(DeployDSLTest.fetch(:env_vars)).to include(
36
+ expect(DeployDSLTest.defined_service.env_vars).to eq(
37
37
  'SHAKESPEARE' => 'Hamlet',
38
38
  'DICKENS' => 'David Copperfield',
39
39
  'DICKENS_BIRTH_YEAR' => '1812'
40
40
  )
41
41
  end
42
42
 
43
+ describe '#add_capability' do
44
+ it 'adds one capability' do
45
+ DeployDSLTest.add_capability 'IPC_LOCK'
46
+ expect(DeployDSLTest.defined_service.cap_adds).to eq(['IPC_LOCK'])
47
+ end
48
+
49
+ it 'adds multiple capabilites' do
50
+ DeployDSLTest.add_capability 'IPC_LOCK'
51
+ DeployDSLTest.add_capability 'SYS_RESOURCE'
52
+ expect(DeployDSLTest.defined_service.cap_adds).to eq(['IPC_LOCK', 'SYS_RESOURCE'])
53
+ end
54
+
55
+ it 'fails when an invalid capability is added' do
56
+ lambda{ DeployDSLTest.add_capability 'FOO_BAR' }.should raise_error SystemExit
57
+ end
58
+ end
59
+
60
+ describe '#drop_capability' do
61
+ it 'drops one capability' do
62
+ DeployDSLTest.drop_capability 'IPC_LOCK'
63
+ expect(DeployDSLTest.defined_service.cap_drops).to eq(['IPC_LOCK'])
64
+ end
65
+
66
+ it 'drops multiple capabilites' do
67
+ DeployDSLTest.drop_capability 'IPC_LOCK'
68
+ DeployDSLTest.drop_capability 'SYS_RESOURCE'
69
+ expect(DeployDSLTest.defined_service.cap_drops).to eq(['IPC_LOCK', 'SYS_RESOURCE'])
70
+ end
71
+ end
72
+
43
73
  it 'adds hosts to the host list' do
44
74
  DeployDSLTest.set(:hosts, [ 'host1' ])
45
75
  DeployDSLTest.host('host2')
@@ -67,25 +97,16 @@ describe Centurion::DeployDSL do
67
97
  end
68
98
 
69
99
  it 'adds new bind ports to the list' do
70
- dummy_value = { '666/tcp' => ['value'] }
71
- DeployDSLTest.set(:port_bindings, dummy_value)
100
+ DeployDSLTest.host_port(666, container_port: 666)
72
101
  DeployDSLTest.host_port(999, container_port: 80)
73
102
 
74
- expect(DeployDSLTest).to have_key_and_value(
75
- :port_bindings,
76
- dummy_value.merge('80/tcp' => [{ 'HostPort' => '999' }])
77
- )
103
+ expect(DeployDSLTest.defined_service.port_bindings).to eq([Centurion::Service::PortBinding.new(666, 666, 'tcp'), Centurion::Service::PortBinding.new(999, 80, 'tcp')])
78
104
  end
79
105
 
80
106
  it 'adds new bind ports to the list with an IP binding when supplied' do
81
- dummy_value = { '666/tcp' => ['value'] }
82
- DeployDSLTest.set(:port_bindings, dummy_value)
83
107
  DeployDSLTest.host_port(999, container_port: 80, host_ip: '0.0.0.0')
84
108
 
85
- expect(DeployDSLTest).to have_key_and_value(
86
- :port_bindings,
87
- dummy_value.merge('80/tcp' => [{ 'HostIp' => '0.0.0.0', 'HostPort' => '999' }])
88
- )
109
+ expect(DeployDSLTest.defined_service.port_bindings).to eq([Centurion::Service::PortBinding.new(999, 80, 'tcp', '0.0.0.0')])
89
110
  end
90
111
 
91
112
  it 'does not explode if port_bindings is empty' do
@@ -97,6 +118,27 @@ describe Centurion::DeployDSL do
97
118
  end
98
119
  end
99
120
 
121
+ describe '#network_mode' do
122
+ it 'accepts host mode' do
123
+ DeployDSLTest.network_mode('host')
124
+ expect(DeployDSLTest.defined_service.network_mode).to eq('host')
125
+ end
126
+
127
+ it 'accepts bridge mode' do
128
+ DeployDSLTest.network_mode('bridge')
129
+ expect(DeployDSLTest.defined_service.network_mode).to eq('bridge')
130
+ end
131
+
132
+ it 'accepts container link mode' do
133
+ DeployDSLTest.network_mode('container:a2e8937b')
134
+ expect(DeployDSLTest.defined_service.network_mode).to eq('container:a2e8937b')
135
+ end
136
+
137
+ it 'fails when invalid mode is passed' do
138
+ expect { DeployDSLTest.network_mode('foo') }.to raise_error(SystemExit)
139
+ end
140
+ end
141
+
100
142
  describe '#host_volume' do
101
143
  it 'raises unless passed the container_volume option' do
102
144
  expect { DeployDSLTest.host_volume('foo', {}) }.to raise_error(ArgumentError, /:container_volume/)
@@ -110,7 +152,20 @@ describe Centurion::DeployDSL do
110
152
  expect(DeployDSLTest.fetch(:binds)).to be_nil
111
153
  DeployDSLTest.host_volume('volume1', container_volume: '/dev/sdd')
112
154
  DeployDSLTest.host_volume('volume2', container_volume: '/dev/sde')
113
- expect(DeployDSLTest.fetch(:binds)).to eq %w{ volume1:/dev/sdd volume2:/dev/sde }
155
+ expect(DeployDSLTest.defined_service.volumes).to eq [Centurion::Service::Volume.new('volume1', '/dev/sdd'), Centurion::Service::Volume.new('volume2', '/dev/sde')]
156
+ end
157
+ end
158
+
159
+ describe '#extra_host' do
160
+ it 'adds new hosts to the list' do
161
+ DeployDSLTest.extra_host('192.168.33.10', 'newrelic.dev')
162
+ DeployDSLTest.extra_host('192.168.33.93', 'd.newrelic.dev')
163
+
164
+ expect(DeployDSLTest.defined_service.extra_hosts).to eq(['newrelic.dev:192.168.33.10', 'd.newrelic.dev:192.168.33.93'])
165
+ end
166
+
167
+ it 'extra_hosts list is nil by default' do
168
+ expect(DeployDSLTest.defined_service.extra_hosts).to eq(nil)
114
169
  end
115
170
  end
116
171
 
@@ -120,4 +175,10 @@ describe Centurion::DeployDSL do
120
175
 
121
176
  expect(DeployDSLTest.get_current_tags_for('asdf')).to eq [ { server: 'host1', tags: [ 'foo'] } ]
122
177
  end
178
+
179
+ it 'appends tags to the image name when returning a service' do
180
+ DeployDSLTest.set(:tag, 'roland')
181
+ DeployDSLTest.set(:image, 'charlemagne')
182
+ expect(DeployDSLTest.defined_service.image).to eq('charlemagne:roland')
183
+ end
123
184
  end
@@ -6,8 +6,10 @@ describe Centurion::Deploy do
6
6
  let(:mock_bad_status) { double('http_status_ok', status: 500) }
7
7
  let(:server) { double('docker_server', attach: true, hostname: hostname) }
8
8
  let(:port) { 8484 }
9
- let(:container) { { 'Ports' => [{ 'PublicPort' => port }, 'Created' => Time.now.to_i ], 'Id' => '21adfd2ef2ef2349494a', 'Names' => [ 'name1' ] } }
9
+ let(:container_id) { '21adfd2ef2ef2349494a' }
10
+ let(:container) { { 'Ports' => [{ 'PublicPort' => port }, 'Created' => Time.now.to_i ], 'Id' => container_id, 'Names' => [ 'name1' ] } }
10
11
  let(:endpoint) { '/status/check' }
12
+ let(:container_id) { '21adfd2ef2ef2349494a' }
11
13
  let(:test_deploy) do
12
14
  Object.new.tap do |o|
13
15
  o.send(:extend, Centurion::Deploy)
@@ -19,7 +21,6 @@ describe Centurion::Deploy do
19
21
 
20
22
  before do
21
23
  allow(test_deploy).to receive(:fetch).and_return nil
22
- allow(test_deploy).to receive(:fetch).with(:container_hostname, hostname).and_return(hostname)
23
24
  allow(test_deploy).to receive(:host_ip).and_return('172.16.0.1')
24
25
  end
25
26
 
@@ -50,23 +51,16 @@ describe Centurion::Deploy do
50
51
 
51
52
  describe '#container_up?' do
52
53
  it 'recognizes when no containers are running' do
53
- expect(server).to receive(:find_containers_by_public_port).and_return([])
54
+ expect(server).to receive(:find_container_by_id).and_return(nil)
54
55
 
55
- expect(test_deploy.container_up?(server, port)).to be_falsey
56
- end
57
-
58
- it 'complains when more than one container is bound to this port' do
59
- expect(server).to receive(:find_containers_by_public_port).and_return([1,2])
60
- expect(test_deploy).to receive(:error).with /More than one container/
61
-
62
- expect(test_deploy.container_up?(server, port)).to be_falsey
56
+ expect(test_deploy.container_up?(server, container_id)).to be_falsey
63
57
  end
64
58
 
65
59
  it 'recognizes when the container is actually running' do
66
- expect(server).to receive(:find_containers_by_public_port).and_return([container])
60
+ expect(server).to receive(:find_container_by_id).and_return(container)
67
61
  expect(test_deploy).to receive(:info).with /Found container/
68
62
 
69
- expect(test_deploy.container_up?(server, port)).to be_truthy
63
+ expect(test_deploy.container_up?(server, container_id)).to be_truthy
70
64
  end
71
65
  end
72
66
 
@@ -79,7 +73,7 @@ describe Centurion::Deploy do
79
73
  allow(test_deploy).to receive(:container_up?).and_return(true)
80
74
  allow(test_deploy).to receive(:http_status_ok?).and_return(true)
81
75
 
82
- test_deploy.wait_for_health_check_ok(test_deploy.method(:http_status_ok?), server, port, '/foo', 'image_id', 'chaucer')
76
+ test_deploy.wait_for_health_check_ok(test_deploy.method(:http_status_ok?), server, container_id, port, '/foo', 'image_id', 'chaucer')
83
77
  expect(test_deploy).to have_received(:info).with(/Waiting for the port/)
84
78
  expect(test_deploy).to have_received(:info).with('Container is up!')
85
79
  end
@@ -91,7 +85,7 @@ describe Centurion::Deploy do
91
85
  expect(test_deploy).to receive(:exit)
92
86
  expect(test_deploy).to receive(:sleep).with(0)
93
87
 
94
- test_deploy.wait_for_health_check_ok(test_deploy.method(:http_status_ok?), server, port, '/foo', 'image_id', 'chaucer', 0, 1)
88
+ test_deploy.wait_for_health_check_ok(test_deploy.method(:http_status_ok?), server, container_id, port, '/foo', 'image_id', 'chaucer', 0, 1)
95
89
  expect(test_deploy).to have_received(:info).with(/Waiting for the port/)
96
90
  end
97
91
 
@@ -102,40 +96,42 @@ describe Centurion::Deploy do
102
96
  allow(test_deploy).to receive(:warn)
103
97
  expect(test_deploy).to receive(:exit)
104
98
 
105
- test_deploy.wait_for_health_check_ok(test_deploy.method(:http_status_ok?), server, port, '/foo', 'image_id', 'chaucer', 1, 0)
99
+ test_deploy.wait_for_health_check_ok(test_deploy.method(:http_status_ok?), server, container_id, port, '/foo', 'image_id', 'chaucer', 1, 0)
106
100
  expect(test_deploy).to have_received(:info).with(/Waiting for the port/)
107
101
  end
108
102
  end
109
103
 
110
104
  describe '#cleanup_containers' do
111
105
  it 'deletes all but two containers' do
112
- expect(server).to receive(:old_containers_for_port).with(port.to_s).and_return([
113
- {'Id' => '123', 'Names' => ['foo']},
114
- {'Id' => '456', 'Names' => ['foo']},
115
- {'Id' => '789', 'Names' => ['foo']},
116
- {'Id' => '0ab', 'Names' => ['foo']},
117
- {'Id' => 'cde', 'Names' => ['foo']},
106
+ service = Centurion::Service.new('walrus')
107
+ expect(server).to receive(:old_containers_for_name).with('walrus').and_return([
108
+ {'Id' => '123', 'Names' => ['walrus-3bab311b460bdf']},
109
+ {'Id' => '456', 'Names' => ['walrus-4bab311b460bdf']},
110
+ {'Id' => '789', 'Names' => ['walrus-5bab311b460bdf']},
111
+ {'Id' => '0ab', 'Names' => ['walrus-6bab311b460bdf']},
112
+ {'Id' => 'cde', 'Names' => ['walrus-7bab311b460bdf']},
118
113
  ])
119
114
  expect(server).to receive(:remove_container).with('789')
120
115
  expect(server).to receive(:remove_container).with('0ab')
121
116
  expect(server).to receive(:remove_container).with('cde')
122
117
 
123
- test_deploy.cleanup_containers(server, {'80/tcp' => [{'HostIp' => '0.0.0.0', 'HostPort' => port.to_s}]})
118
+ test_deploy.cleanup_containers(server, service)
124
119
  end
125
120
  end
126
121
 
127
122
  describe '#stop_containers' do
128
123
  it 'calls stop_container on the right containers' do
124
+ service = Centurion::Service.new(:centurion)
125
+ service.add_port_bindings(80, 80)
126
+
129
127
  second_container = container.dup
130
128
  containers = [ container, second_container ]
131
- bindings = {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]}
132
129
 
133
- expect(server).to receive(:find_containers_by_public_port).and_return(containers)
134
- expect(test_deploy).to receive(:public_port_for).with(bindings).and_return('80')
130
+ expect(server).to receive(:find_containers_by_public_port).with(80).and_return(containers)
135
131
  expect(server).to receive(:stop_container).with(container['Id'], 30).once
136
132
  expect(server).to receive(:stop_container).with(second_container['Id'], 30).once
137
133
 
138
- test_deploy.stop_containers(server, bindings)
134
+ test_deploy.stop_containers(server, service)
139
135
  end
140
136
  end
141
137
 
@@ -149,348 +145,54 @@ describe Centurion::Deploy do
149
145
  end
150
146
  end
151
147
 
152
- describe '#container_config_for' do
153
- let(:image_id) { 'image_id' }
154
- let(:port_bindings) { nil }
155
- let(:env) { nil }
156
- let(:volumes) { nil }
157
- let(:command) { nil }
158
-
159
- it 'works without env_vars, port_bindings, or a command' do
160
- config = test_deploy.container_config_for(server, image_id)
161
-
162
- expect(config).to be_a(Hash)
163
- expect(config.keys).to match_array(%w{ Hostname Image })
164
- end
165
-
166
- context 'when port bindings are specified' do
167
- let(:port_bindings) { {1234 => 80, 9876 => 80} }
168
-
169
- it 'sets the ExposedPorts key in the config correctly' do
170
- config = test_deploy.container_config_for(server, image_id, port_bindings)
171
-
172
- expect(config['ExposedPorts']).to be_a(Hash)
173
- expect(config['ExposedPorts'].keys).to eq port_bindings.keys
174
- end
175
- end
176
-
177
- context 'when env vars are specified' do
178
- let(:env) { {
179
- 'FOO' => 'BAR',
180
- 'BAZ' => '%DOCKER_HOSTNAME%.example.com',
181
- 'BAR' => '%DOCKER_HOST_IP%:1234'
182
- } }
183
-
184
- it 'sets the Env key in the config' do
185
- config = test_deploy.container_config_for(server, image_id, port_bindings, env)
186
-
187
- expect(config).to be_a(Hash)
188
- expect(config.keys).to match_array(%w{ Hostname Image Env })
189
- expect(config['Env']).to include('FOO=BAR')
190
- end
191
-
192
- it 'interpolates the hostname into env_vars' do
193
- config = test_deploy.container_config_for(server, image_id, port_bindings, env)
194
-
195
- expect(config['Env']).to include('BAZ=host1.example.com')
196
- end
197
-
198
- it 'interpolates the host IP into the env_vars' do
199
- config = test_deploy.container_config_for(server, image_id, port_bindings, env)
200
-
201
- expect(config['Env']).to include('BAR=172.16.0.1:1234')
202
- end
203
- end
204
-
205
- context 'when volumes are specified' do
206
- let(:volumes) { ["/tmp/foo:/tmp/chaucer"] }
207
-
208
- it 'sets the Volumes key in the config' do
209
- config = test_deploy.container_config_for(server, image_id, port_bindings, env, volumes)
210
-
211
- expect(config).to be_a(Hash)
212
- expect(config.keys).to match_array(%w{ Hostname Image Volumes VolumesFrom })
213
- expect(config['Volumes']['/tmp/chaucer']).to eq({})
214
- end
148
+ describe '#hostname_proc' do
149
+ it 'does not provide a container hostname if no override is given' do
150
+ expect(test_deploy).to receive(:fetch).with(:container_hostname).and_return nil
151
+ expect(test_deploy.hostname_proc).to be_nil
215
152
  end
216
153
 
217
- context 'when a custom command is specified' do
218
- let(:command) { ['/bin/echo', 'hi'] }
219
-
220
- it 'sets the Cmd key in the config' do
221
- config = test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command)
222
-
223
- expect(config).to be_a(Hash)
224
- expect(config.keys).to match_array(%w{ Hostname Image Cmd })
225
- expect(config['Cmd']).to eq(command)
226
- end
154
+ it 'provides container hostname if an override string is given' do
155
+ expect(test_deploy).to receive(:fetch).with(:container_hostname).and_return 'example.com'
156
+ expect(test_deploy.hostname_proc.call('foo')).to eq('example.com')
227
157
  end
228
158
 
229
- context 'when cgroup limits are specified' do
230
- let(:memory) { 10000000 }
231
- let(:cpu_shares) { 1234 }
232
-
233
- before do
234
- allow(test_deploy).to receive(:error) # Make sure we don't have red output in tests
235
- end
236
-
237
- it 'sets cgroup limits in the config' do
238
- config = test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, cpu_shares)
239
-
240
- expect(config).to be_a(Hash)
241
- expect(config.keys).to match_array(%w{ Hostname Image Memory CpuShares })
242
- expect(config['Memory']).to eq(10000000)
243
- expect(config['CpuShares']).to eq(1234)
244
- end
245
-
246
- it 'throws a fatal error value for Cgroup Memory limit is invalid' do
247
- expect { config = test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, "I like pie", cpu_shares) }.to terminate.with_code(102)
248
- expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, -100, cpu_shares) }.to terminate.with_code(102)
249
- expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, 0xFFFFFFFFFFFFFFFFFF, cpu_shares) }.to terminate.with_code(102)
250
- end
251
-
252
- it 'throws a fatal error value for Cgroup CPU limit is invalid' do
253
- expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, "I like pie") }.to terminate.with_code(101)
254
- expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, -100) }.to terminate.with_code(101)
255
- expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, 0xFFFFFFFFFFFFFFFFFF) }.to terminate.with_code(101)
256
- end
257
-
258
- it 'still works when memory is specified in gigabytes' do
259
- memory = 3.gigabytes
260
- config = test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, cpu_shares)
261
- expect(config['Memory']).to eq(3 * 1024 * 1024 * 1024)
159
+ context 'container_hostname is overridden with a proc' do
160
+ it 'provides a container hostname by executing the proc given' do
161
+ expect(test_deploy).to receive(:fetch).with(:container_hostname).and_return ->(s) { "container.#{s}" }
162
+ expect(test_deploy.hostname_proc.call('example.com')).to eq('container.example.com')
262
163
  end
263
164
  end
264
165
  end
265
166
 
266
- describe '#start_container_with_config' do
267
- let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
268
-
269
- it 'pass host_config to start_container' do
270
- allow(server).to receive(:container_config_for).and_return({
271
- 'Image' => 'image_id',
272
- 'Hostname' => server.hostname,
273
- })
274
-
275
- allow(server).to receive(:create_container).and_return({
276
- 'Id' => 'abc123456'
277
- })
278
-
279
- allow(server).to receive(:inspect_container)
280
-
281
- allow(test_deploy).to receive(:fetch).with(:custom_dns).and_return('8.8.8.8')
282
- allow(test_deploy).to receive(:fetch).with(:name).and_return(nil)
283
-
284
- expect(server).to receive(:start_container).with(
285
- 'abc123456',
286
- {
287
- 'PortBindings' => bindings,
288
- 'Dns' => '8.8.8.8',
289
- 'RestartPolicy' => {
290
- 'Name' => 'on-failure',
291
- 'MaximumRetryCount' => 10
292
- }
293
- }
294
- ).once
295
-
296
- test_deploy.start_new_container(server, 'image_id', bindings, {}, nil, nil)
297
- end
298
- end
299
-
300
- describe '#start_container_with_restart_policy' do
301
- let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
302
-
303
- before do
304
- allow(server).to receive(:container_config_for).and_return({
305
- 'Image' => 'image_id',
306
- 'Hostname' => server.hostname,
307
- })
308
-
309
- allow(server).to receive(:create_container).and_return({
310
- 'Id' => 'abc123456'
311
- })
312
-
313
- allow(server).to receive(:inspect_container)
314
- end
315
-
316
- it 'pass no restart policy to start_container' do
317
- expect(server).to receive(:start_container).with(
318
- 'abc123456',
319
- {
320
- 'PortBindings' => bindings,
321
- 'RestartPolicy' => {
322
- 'Name' => 'on-failure',
323
- 'MaximumRetryCount' => 10
324
- }
325
- }
326
- ).once
327
-
328
- test_deploy.start_new_container(server, 'image_id', bindings, {}, nil, nil)
329
- end
330
-
331
- it 'pass "always" restart policy to start_container' do
332
- allow(test_deploy).to receive(:fetch).with(:restart_policy_name).and_return('always')
333
-
334
- expect(server).to receive(:start_container).with(
335
- 'abc123456',
336
- {
337
- 'PortBindings' => bindings,
338
- 'RestartPolicy' => {
339
- 'Name' => 'always'
340
- }
341
- }
342
- ).once
343
-
344
- test_deploy.start_new_container(server, 'image_id', bindings, {}, nil, nil)
345
- end
346
-
347
- it 'pass "on-failure with 50 retries" restart policy to start_container' do
348
- allow(test_deploy).to receive(:fetch).with(:restart_policy_name).and_return('on-failure')
349
- allow(test_deploy).to receive(:fetch).with(:restart_policy_max_retry_count).and_return(50)
350
-
351
- expect(server).to receive(:start_container).with(
352
- 'abc123456',
353
- {
354
- 'PortBindings' => bindings,
355
- 'RestartPolicy' => {
356
- 'Name' => 'on-failure',
357
- 'MaximumRetryCount' => 50
358
- }
359
- }
360
- ).once
361
-
362
- test_deploy.start_new_container(server, 'image_id', bindings, {}, nil, nil)
363
- end
364
-
365
- it 'pass "no" restart policy to start_container' do
366
- allow(test_deploy).to receive(:fetch).with(:restart_policy_name).and_return('no')
367
-
368
- expect(server).to receive(:start_container).with(
369
- 'abc123456',
370
- {
371
- 'PortBindings' => bindings,
372
- 'RestartPolicy' => {
373
- 'Name' => 'no'
374
- }
375
- }
376
- ).once
377
-
378
- test_deploy.start_new_container(server, 'image_id', bindings, {}, nil, nil)
379
- end
380
-
381
- it 'pass "garbage" restart policy to start_container' do
382
- allow(test_deploy).to receive(:fetch).with(:restart_policy_name).and_return('garbage')
383
-
384
- expect(server).to receive(:start_container).with(
385
- 'abc123456',
386
- {
387
- 'PortBindings' => bindings,
388
- 'RestartPolicy' => {
389
- 'Name' => 'on-failure',
390
- 'MaximumRetryCount' => 10
391
- }
392
- }
393
- ).once
394
-
395
- test_deploy.start_new_container(server, 'image_id', bindings, {}, nil, nil)
396
- end
397
- end
398
-
399
167
  describe '#start_new_container' do
400
168
  let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
401
169
  let(:env) { { 'FOO' => 'BAR' } }
402
170
  let(:volumes) { ['/foo:/bar'] }
403
171
  let(:command) { ['/bin/echo', 'hi'] }
404
172
 
405
- it 'configures the container' do
406
- expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, nil, {}, nil, nil, nil).once
407
-
408
- allow(test_deploy).to receive(:start_container_with_config)
409
-
410
- test_deploy.start_new_container(server, 'image_id', bindings, {}, nil)
411
- end
412
-
413
- it 'starts the container' do
414
- expect(test_deploy).to receive(:start_container_with_config).with(server, {}, anything(), anything())
415
-
416
- test_deploy.start_new_container(server, 'image_id', bindings, {})
417
- end
418
-
419
- it 'sets the container hostname when asked' do
420
- allow(test_deploy).to receive(:fetch).with(:container_hostname, anything()).and_return('chaucer')
421
-
422
- expect(server).to receive(:create_container).with(
423
- hash_including(
424
- 'Image' => 'image_id',
425
- 'Hostname' => 'chaucer',
426
- 'ExposedPorts' => {'80/tcp'=>{}},
427
- 'Cmd' => command,
428
- 'Env' => ['FOO=BAR'],
429
- 'Volumes' => {'/bar' => {}},
430
- ),
431
- nil
432
- ).and_return(container)
433
-
434
- expect(server).to receive(:start_container)
435
- expect(server).to receive(:inspect_container)
436
- test_deploy.start_new_container(server, 'image_id', bindings, volumes, env, command)
437
- end
438
-
439
173
  it 'ultimately asks the server object to do the work' do
440
- allow(test_deploy).to receive(:fetch).with(:custom_dns).and_return(nil)
441
- allow(test_deploy).to receive(:fetch).with(:name).and_return('app1')
442
-
443
- expect(server).to receive(:create_container).with(
444
- hash_including(
445
- 'Image' => 'image_id',
446
- 'Hostname' => hostname,
447
- 'ExposedPorts' => {'80/tcp'=>{}},
448
- 'Cmd' => command,
449
- 'Env' => ['FOO=BAR'],
450
- 'Volumes' => {'/bar' => {}},
451
- ),
452
- 'app1'
453
- ).and_return(container)
174
+ service = double(:Service, name: :centurion, build_config: {"Image" => "abcdef"}, build_host_config: {})
175
+ restart_policy = double(:RestartPolicy)
176
+
177
+ expect(server).to receive(:create_container).with({"Image" => "abcdef"}, :centurion).and_return(container)
454
178
 
455
179
  expect(server).to receive(:start_container)
456
180
  expect(server).to receive(:inspect_container)
457
181
 
458
- new_container = test_deploy.start_new_container(server, 'image_id', bindings, volumes, env, command)
182
+ new_container = test_deploy.start_new_container(server, service, restart_policy)
459
183
  expect(new_container).to eq(container)
460
184
  end
461
185
  end
462
186
 
463
187
  describe '#launch_console' do
464
- let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
465
- let(:volumes) { nil }
466
- let(:env) { nil }
467
- let(:command) { nil }
468
- let(:memory) { nil }
469
- let(:cpu_shares) { nil }
470
-
471
- it 'configures the container' do
472
- expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, env, volumes, command, memory, cpu_shares).once
473
- allow(test_deploy).to receive(:start_container_with_config)
474
-
475
- test_deploy.start_new_container(server, 'image_id', bindings, volumes, env, command, memory, cpu_shares)
476
- end
477
-
478
- it 'augments the container_config' do
479
- expect(test_deploy).to receive(:start_container_with_config).with(server, volumes,
480
- anything(),
481
- hash_including('Cmd' => [ '/bin/bash' ], 'AttachStdin' => true , 'Tty' => true , 'OpenStdin' => true)
482
- ).and_return({'Id' => 'shakespeare'})
483
-
484
- test_deploy.launch_console(server, 'image_id', bindings, volumes, env)
485
- end
486
-
487
188
  it 'starts the console' do
488
- expect(test_deploy).to receive(:start_container_with_config).with(
489
- server, nil, anything(), anything()
490
- ).and_return({'Id' => 'shakespeare'})
189
+ service = double(:Service, name: :centurion, build_console_config: {"Image" => "abcdef"}, build_host_config: {})
190
+
191
+ expect(server).to receive(:create_container).with({"Image" => "abcdef"}, :centurion).and_return(container)
192
+ expect(server).to receive(:start_container)
491
193
 
492
- test_deploy.launch_console(server, 'image_id', bindings, volumes, env)
493
- expect(server).to have_received(:attach).with('shakespeare')
194
+ test_deploy.launch_console(server, service)
195
+ expect(server).to have_received(:attach).with(container_id)
494
196
  end
495
197
  end
496
198
  end