centurion 1.4.2 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +41 -5
- data/bin/centurion +19 -10
- data/lib/centurion/deploy.rb +29 -3
- data/lib/centurion/deploy_dsl.rb +8 -0
- data/lib/centurion/docker_server.rb +12 -4
- data/lib/centurion/version.rb +1 -1
- data/lib/tasks/deploy.rake +6 -2
- data/spec/deploy_spec.rb +40 -7
- data/spec/docker_server_spec.rb +35 -1
- data/spec/support/matchers/exit_code_matches.rb +38 -0
- metadata +4 -2
data/README.md
CHANGED
@@ -25,7 +25,7 @@ Commercial Docker Registry Providers:
|
|
25
25
|
Open-source:
|
26
26
|
- The [Docker registry](https://github.com/dotcloud/docker-registry) project,
|
27
27
|
built and maintained by Docker. You host this yourself.
|
28
|
-
- (*NEW!*) [Dogestry](https://github.com/
|
28
|
+
- (*NEW!*) [Dogestry](https://github.com/dogestry/dogestry) is an
|
29
29
|
s3-backed Docker registry alternative that removes the requirement to set up
|
30
30
|
a centralized registry service or host anything yourself.
|
31
31
|
|
@@ -184,6 +184,23 @@ This is currently pretty inflexible in that the hostname will now be the same
|
|
184
184
|
on all of your hosts. A possible future addition is the ability to pass
|
185
185
|
a block to be evaluated on each host.
|
186
186
|
|
187
|
+
### CGroup Resource Constraints
|
188
|
+
|
189
|
+
Limits on memory and CPU can be specified with the `memory` and `cpu_shares`
|
190
|
+
settings. Both of these expect a 64-bit integer describing the number of
|
191
|
+
bytes, and the number of CPU shares, respectively.
|
192
|
+
|
193
|
+
For example, to limit the memory to 1G, and the cpu time slice to half the
|
194
|
+
normal length, include the following:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
memory 1024000000
|
198
|
+
cpu_shares 512
|
199
|
+
```
|
200
|
+
|
201
|
+
For more information on Docker's CGroup limits see [the Docker
|
202
|
+
docs](https://docs.docker.com/reference/run/#runtime-constraints-on-cpu-and-memory).
|
203
|
+
|
187
204
|
### Interpolation
|
188
205
|
|
189
206
|
Currently there is one special string for interpolation that can be added to
|
@@ -317,7 +334,7 @@ $ bundle exec centurion -p radio-radio -e staging -a list
|
|
317
334
|
|
318
335
|
Centurion needs to have access to some registry in order to pull images to
|
319
336
|
remote Docker servers. This needs to be either a hosted registry (public or
|
320
|
-
private), or [Dogestry](https://github.com/
|
337
|
+
private), or [Dogestry](https://github.com/dogestry/dogestry).
|
321
338
|
|
322
339
|
#### Access to the registry
|
323
340
|
|
@@ -340,7 +357,7 @@ These correspond to the following settings:
|
|
340
357
|
Centurion normally uses the built-in registry support in the Docker daemon to
|
341
358
|
handle pushing and pulling images. But Centurion also has the ability to use
|
342
359
|
external tooling to support hosting your registry on Amazon S3. That tooling is
|
343
|
-
from a project called [Dogestry](https://github.com/
|
360
|
+
from a project called [Dogestry](https://github.com/dogestry/dogestry).
|
344
361
|
We have recently improved that tooling substantially in coordination with the
|
345
362
|
Centurion support.
|
346
363
|
|
@@ -349,7 +366,7 @@ with Amazon S3 to provide reliable hosting of images. Setting Centurion up to
|
|
349
366
|
use Dogestry is pretty trivial:
|
350
367
|
|
351
368
|
1. Install Dogestry binaries on the client from which Dogestry is run.
|
352
|
-
Binaries are provided in the [GitHub release](https://github.com/
|
369
|
+
Binaries are provided in the [GitHub release](https://github.com/dogestry/dogestry).
|
353
370
|
1. Add the settings necessary to get Centurion to pull from Dogestry. A config
|
354
371
|
example is provided below:
|
355
372
|
|
@@ -365,6 +382,24 @@ namespace :environment do
|
|
365
382
|
end
|
366
383
|
```
|
367
384
|
|
385
|
+
Development
|
386
|
+
-----------
|
387
|
+
|
388
|
+
Sometimes when you're doing development you want to try out some configuration
|
389
|
+
settings in environment variables that aren't in the config yet. Or perhaps you
|
390
|
+
want to override existing settings to test with. You can provide the
|
391
|
+
`--override-env` command line flag with some overrides or new variables to set.
|
392
|
+
Here's how to use it:
|
393
|
+
|
394
|
+
```bash
|
395
|
+
$ centurion -e development -a deploy -p radio-radio --override-env=SERVICE_PORT=8080,NAME=radio
|
396
|
+
```
|
397
|
+
|
398
|
+
Centurion is aimed at repeatable deployments so we don't recommend that you use
|
399
|
+
this functionality for production deployments. It will work, but it means that
|
400
|
+
the config is not the whole source of truth for your container configuration.
|
401
|
+
Caveat emptor.
|
402
|
+
|
368
403
|
Future Additions
|
369
404
|
----------------
|
370
405
|
|
@@ -388,6 +423,7 @@ Pull requests should:
|
|
388
423
|
* Have a description that explains the need for the changes
|
389
424
|
* Include tests!
|
390
425
|
* Not break the public API
|
426
|
+
* Add yourself to the CONTRIBUTORS file. I might forget.
|
391
427
|
|
392
428
|
If you are simply looking to contribute to the project, taking on one of the
|
393
429
|
items in the "Future Additions" section above would be a great place to start.
|
@@ -400,4 +436,4 @@ patents, and ideas in that code in our products if we so choose. You also agree
|
|
400
436
|
the code is provided as-is and you provide no warranties as to its fitness or
|
401
437
|
correctness for any purpose
|
402
438
|
|
403
|
-
Copyright (c) 2014 New Relic, Inc. All rights reserved.
|
439
|
+
Copyright (c) 2014-2015 New Relic, Inc. All rights reserved.
|
data/bin/centurion
CHANGED
@@ -22,16 +22,17 @@ Dir.glob(File.join(task_dir, '*.rake')).each { |file| load file }
|
|
22
22
|
require 'trollop'
|
23
23
|
|
24
24
|
opts = Trollop::options do
|
25
|
-
opt :project, 'project (blog, forums...)',
|
26
|
-
opt :environment, "environment (production, staging...)",
|
27
|
-
opt :action, 'action (deploy, list...)',
|
28
|
-
opt :image, 'image (yourco/project...)',
|
29
|
-
opt :tag, 'tag (latest...)',
|
30
|
-
opt :hosts, 'hosts, comma separated',
|
31
|
-
opt :docker_path, 'path to docker executable
|
32
|
-
opt :no_pull, 'Skip the pull_image step',
|
33
|
-
opt :registry_user, 'user for registry auth
|
34
|
-
opt :registry_password,'password for registry auth
|
25
|
+
opt :project, 'project (blog, forums...)', type: String, required: true, short: '-p'
|
26
|
+
opt :environment, "environment (production, staging...)", type: String, required: true, short: '-e'
|
27
|
+
opt :action, 'action (deploy, list...)', type: String, default: 'list', short: '-a'
|
28
|
+
opt :image, 'image (yourco/project...)', type: String, short: '-i'
|
29
|
+
opt :tag, 'tag (latest...)', type: String, short: '-t'
|
30
|
+
opt :hosts, 'hosts, comma separated', type: String, short: '-h'
|
31
|
+
opt :docker_path, 'path to docker executable', type: String, default: 'docker', short: '-d'
|
32
|
+
opt :no_pull, 'Skip the pull_image step', type: :flag, default: false, short: '-n'
|
33
|
+
opt :registry_user, 'user for registry auth', type: String, short: :none
|
34
|
+
opt :registry_password,'password for registry auth', type: String, short: :none
|
35
|
+
opt :override_env, 'override environment variables, comma separated', type: String
|
35
36
|
end
|
36
37
|
|
37
38
|
set_current_environment(opts[:environment].to_sym)
|
@@ -55,6 +56,14 @@ set :image, opts[:image] if opts[:image]
|
|
55
56
|
set :tag, opts[:tag] if opts[:tag]
|
56
57
|
set :hosts, opts[:hosts].split(",") if opts[:hosts]
|
57
58
|
|
59
|
+
# Override environment variables when specified
|
60
|
+
if opts[:override_env]
|
61
|
+
opts[:override_env].split(',').each do |envvar|
|
62
|
+
key, value = envvar.split('=')
|
63
|
+
env_vars(key => value)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
58
67
|
# Default tag should be "latest"
|
59
68
|
set :tag, 'latest' unless any?(:tag)
|
60
69
|
set :docker_registry, Centurion::DockerRegistry::OFFICIAL_URL unless any?(:docker_registry)
|
data/lib/centurion/deploy.rb
CHANGED
@@ -4,6 +4,8 @@ module Centurion; end
|
|
4
4
|
|
5
5
|
module Centurion::Deploy
|
6
6
|
FAILED_CONTAINER_VALIDATION = 100
|
7
|
+
INVALID_CGROUP_CPUSHARES_VALUE = 101
|
8
|
+
INVALID_CGROUP_MEMORY_VALUE = 102
|
7
9
|
|
8
10
|
def stop_containers(target_server, port_bindings, timeout = 30)
|
9
11
|
public_port = public_port_for(port_bindings)
|
@@ -71,6 +73,17 @@ module Centurion::Deploy
|
|
71
73
|
false
|
72
74
|
end
|
73
75
|
|
76
|
+
def is_a_uint64?(value)
|
77
|
+
result = false
|
78
|
+
if !value.is_a? Integer
|
79
|
+
return result
|
80
|
+
end
|
81
|
+
if value < 0 || value > 0xFFFFFFFFFFFFFFFF
|
82
|
+
return result
|
83
|
+
end
|
84
|
+
return true
|
85
|
+
end
|
86
|
+
|
74
87
|
def wait_for_load_balancer_check_interval
|
75
88
|
sleep(fetch(:rolling_deploy_check_interval, 5))
|
76
89
|
end
|
@@ -87,13 +100,26 @@ module Centurion::Deploy
|
|
87
100
|
end
|
88
101
|
end
|
89
102
|
|
90
|
-
def container_config_for(target_server, image_id, port_bindings=nil, env_vars=nil, volumes=nil, command=nil,
|
103
|
+
def container_config_for(target_server, image_id, port_bindings=nil, env_vars=nil, volumes=nil, command=nil, memory=nil, cpu_shares=nil)
|
104
|
+
|
105
|
+
if memory && ! is_a_uint64?(memory)
|
106
|
+
error "Invalid value for CGroup memory constraint: #{memory}, value must be a between 0 and 18446744073709551615"
|
107
|
+
exit(INVALID_CGROUP_MEMORY_VALUE)
|
108
|
+
end
|
109
|
+
|
110
|
+
if cpu_shares && ! is_a_uint64?(cpu_shares)
|
111
|
+
error "Invalid value for CGroup CPU constraint: #{cpu_shares}, value must be between 0 and 18446744073709551615"
|
112
|
+
exit(INVALID_CGROUP_CPUSHARES_VALUE)
|
113
|
+
end
|
114
|
+
|
91
115
|
container_config = {
|
92
116
|
'Image' => image_id,
|
93
117
|
'Hostname' => fetch(:container_hostname, target_server.hostname),
|
94
118
|
}
|
95
119
|
|
96
120
|
container_config.merge!('Cmd' => command) if command
|
121
|
+
container_config.merge!('Memory' => memory) if memory
|
122
|
+
container_config.merge!('CpuShares' => cpu_shares) if cpu_shares
|
97
123
|
|
98
124
|
if port_bindings
|
99
125
|
container_config['ExposedPorts'] ||= {}
|
@@ -119,8 +145,8 @@ module Centurion::Deploy
|
|
119
145
|
container_config
|
120
146
|
end
|
121
147
|
|
122
|
-
def start_new_container(target_server, image_id, port_bindings, volumes, env_vars=nil, command=nil)
|
123
|
-
container_config = container_config_for(target_server, image_id, port_bindings, env_vars, volumes, command)
|
148
|
+
def start_new_container(target_server, image_id, port_bindings, volumes, env_vars=nil, command=nil, memory=nil, cpu_shares=nil)
|
149
|
+
container_config = container_config_for(target_server, image_id, port_bindings, env_vars, volumes, command, memory, cpu_shares)
|
124
150
|
start_container_with_config(target_server, volumes, port_bindings, container_config)
|
125
151
|
end
|
126
152
|
|
data/lib/centurion/deploy_dsl.rb
CHANGED
@@ -34,10 +34,18 @@ class Centurion::DockerServer
|
|
34
34
|
|
35
35
|
def find_containers_by_public_port(public_port, type='tcp')
|
36
36
|
ps.select do |container|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
next unless container && container['Ports']
|
38
|
+
container['Ports'].find do |port|
|
39
|
+
port['PublicPort'] == public_port.to_i && port['Type'] == type
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_containers_by_name(wanted_name)
|
45
|
+
ps.select do |container|
|
46
|
+
next unless container && container['Names']
|
47
|
+
container['Names'].find do |name|
|
48
|
+
name =~ /\A\/#{wanted_name}(-[a-f0-9]{7})?\Z/
|
41
49
|
end
|
42
50
|
end
|
43
51
|
end
|
data/lib/centurion/version.rb
CHANGED
data/lib/tasks/deploy.rake
CHANGED
@@ -91,7 +91,9 @@ namespace :deploy do
|
|
91
91
|
fetch(:port_bindings),
|
92
92
|
fetch(:binds),
|
93
93
|
fetch(:env_vars),
|
94
|
-
fetch(:command)
|
94
|
+
fetch(:command),
|
95
|
+
fetch(:memory),
|
96
|
+
fetch(:cpu_shares)
|
95
97
|
)
|
96
98
|
end
|
97
99
|
end
|
@@ -118,7 +120,9 @@ namespace :deploy do
|
|
118
120
|
fetch(:port_bindings),
|
119
121
|
fetch(:binds),
|
120
122
|
fetch(:env_vars),
|
121
|
-
fetch(:command)
|
123
|
+
fetch(:command),
|
124
|
+
fetch(:memory),
|
125
|
+
fetch(:cpu_shares)
|
122
126
|
)
|
123
127
|
|
124
128
|
skip_ports = Array(fetch(:rolling_deploy_skip_ports, [])).map(&:to_s)
|
data/spec/deploy_spec.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'spec_helper'
|
1
2
|
require 'centurion/deploy'
|
2
3
|
require 'centurion/deploy_dsl'
|
3
4
|
require 'centurion/logging'
|
@@ -217,6 +218,36 @@ describe Centurion::Deploy do
|
|
217
218
|
expect(config['Cmd']).to eq(command)
|
218
219
|
end
|
219
220
|
end
|
221
|
+
|
222
|
+
context 'when cgroup limits are specified' do
|
223
|
+
let(:memory) { 10000000 }
|
224
|
+
let(:cpu_shares) { 1234 }
|
225
|
+
|
226
|
+
before do
|
227
|
+
allow(test_deploy).to receive(:error) # Make sure we don't have red output in tests
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'sets cgroup limits in the config' do
|
231
|
+
config = test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, cpu_shares)
|
232
|
+
|
233
|
+
expect(config).to be_a(Hash)
|
234
|
+
expect(config.keys).to match_array(%w{ Hostname Image Memory CpuShares })
|
235
|
+
expect(config['Memory']).to eq(10000000)
|
236
|
+
expect(config['CpuShares']).to eq(1234)
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'throws a fatal error value for Cgroup Memory limit is invalid' do
|
240
|
+
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)
|
241
|
+
expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, -100, cpu_shares) }.to terminate.with_code(102)
|
242
|
+
expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, 0xFFFFFFFFFFFFFFFFFF, cpu_shares) }.to terminate.with_code(102)
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'throws a fatal error value for Cgroup CPU limit is invalid' do
|
246
|
+
expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, "I like pie") }.to terminate.with_code(101)
|
247
|
+
expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, -100) }.to terminate.with_code(101)
|
248
|
+
expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, 0xFFFFFFFFFFFFFFFFFF) }.to terminate.with_code(101)
|
249
|
+
end
|
250
|
+
end
|
220
251
|
end
|
221
252
|
|
222
253
|
describe '#start_container_with_config' do
|
@@ -359,7 +390,7 @@ describe Centurion::Deploy do
|
|
359
390
|
let(:command) { ['/bin/echo', 'hi'] }
|
360
391
|
|
361
392
|
it 'configures the container' do
|
362
|
-
expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, nil, {}, nil).once
|
393
|
+
expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, nil, {}, nil, nil, nil).once
|
363
394
|
|
364
395
|
allow(test_deploy).to receive(:start_container_with_config)
|
365
396
|
|
@@ -417,16 +448,18 @@ describe Centurion::Deploy do
|
|
417
448
|
end
|
418
449
|
|
419
450
|
describe '#launch_console' do
|
420
|
-
let(:bindings)
|
421
|
-
let(:volumes)
|
422
|
-
let(:env)
|
423
|
-
let(:command)
|
451
|
+
let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
|
452
|
+
let(:volumes) { nil }
|
453
|
+
let(:env) { nil }
|
454
|
+
let(:command) { nil }
|
455
|
+
let(:memory) { nil }
|
456
|
+
let(:cpu_shares) { nil }
|
424
457
|
|
425
458
|
it 'configures the container' do
|
426
|
-
expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, env, volumes, command).once
|
459
|
+
expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, env, volumes, command, memory, cpu_shares).once
|
427
460
|
allow(test_deploy).to receive(:start_container_with_config)
|
428
461
|
|
429
|
-
test_deploy.start_new_container(server, 'image_id', bindings, volumes, env, command)
|
462
|
+
test_deploy.start_new_container(server, 'image_id', bindings, volumes, env, command, memory, cpu_shares)
|
430
463
|
end
|
431
464
|
|
432
465
|
it 'augments the container_config' do
|
data/spec/docker_server_spec.rb
CHANGED
@@ -2,9 +2,21 @@ require 'spec_helper'
|
|
2
2
|
require 'centurion/docker_server'
|
3
3
|
|
4
4
|
describe Centurion::DockerServer do
|
5
|
-
let(:host)
|
5
|
+
let(:host) { 'host1' }
|
6
6
|
let(:docker_path) { 'docker' }
|
7
7
|
let(:server) { Centurion::DockerServer.new(host, docker_path) }
|
8
|
+
let(:container) {
|
9
|
+
{
|
10
|
+
'Command' => '/bin/bash',
|
11
|
+
'Created' => 1414797234,
|
12
|
+
'Id' => '28970c706db0f69716af43527ed926acbd82581e1cef5e4e6ff152fce1b79972',
|
13
|
+
'Image' => 'centurion-test:latest',
|
14
|
+
'Names' => ['/centurion-783aac4'],
|
15
|
+
'Ports' => [{'PrivatePort'=>80, 'Type'=>'tcp', 'IP'=>'0.0.0.0', 'PublicPort'=>23235}],
|
16
|
+
'Status' => 'Up 3 days'
|
17
|
+
}
|
18
|
+
}
|
19
|
+
let(:ps) { [ container, {}, nil ] }
|
8
20
|
|
9
21
|
it 'knows its hostname' do
|
10
22
|
expect(server.hostname).to eq('host1')
|
@@ -40,4 +52,26 @@ describe Centurion::DockerServer do
|
|
40
52
|
allow(server).to receive(:ps).and_return(image_names.map {|name| { 'Image' => name } })
|
41
53
|
expect(server.current_tags_for('target')).to eq(%w[latest production])
|
42
54
|
end
|
55
|
+
|
56
|
+
context 'finding containers' do
|
57
|
+
before do
|
58
|
+
allow(server).to receive(:ps).and_return(ps)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'finds containers by port' do
|
62
|
+
expect(server.find_containers_by_public_port(23235, 'tcp')).to eq([container])
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'only returns correct matches by port' do
|
66
|
+
expect(server.find_containers_by_public_port(1234, 'tcp')).to be_empty
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'finds containers by name' do
|
70
|
+
expect(server.find_containers_by_name('centurion')).to eq([container])
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'only returns correct matches by name' do
|
74
|
+
expect(server.find_containers_by_name('fbomb')).to be_empty
|
75
|
+
end
|
76
|
+
end
|
43
77
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# https://gist.github.com/mmasashi/58bd7e2668836a387856
|
2
|
+
RSpec::Matchers.define :terminate do |code|
|
3
|
+
actual = nil
|
4
|
+
|
5
|
+
def supports_block_expectations?
|
6
|
+
true
|
7
|
+
end
|
8
|
+
|
9
|
+
match do |block|
|
10
|
+
begin
|
11
|
+
block.call
|
12
|
+
rescue SystemExit => e
|
13
|
+
actual = e.status
|
14
|
+
end
|
15
|
+
actual and actual == status_code
|
16
|
+
end
|
17
|
+
|
18
|
+
chain :with_code do |status_code|
|
19
|
+
@status_code = status_code
|
20
|
+
end
|
21
|
+
|
22
|
+
failure_message do |block|
|
23
|
+
"expected block to call exit(#{status_code}) but exit" +
|
24
|
+
(actual.nil? ? " not called" : "(#{actual}) was called")
|
25
|
+
end
|
26
|
+
|
27
|
+
failure_message_when_negated do |block|
|
28
|
+
"expected block not to call exit(#{status_code})"
|
29
|
+
end
|
30
|
+
|
31
|
+
description do
|
32
|
+
"expect block to call exit(#{status_code})"
|
33
|
+
end
|
34
|
+
|
35
|
+
def status_code
|
36
|
+
@status_code ||= 0
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: centurion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -21,7 +21,7 @@ authors:
|
|
21
21
|
autorequire:
|
22
22
|
bindir: bin
|
23
23
|
cert_chain: []
|
24
|
-
date: 2015-
|
24
|
+
date: 2015-02-24 00:00:00.000000000 Z
|
25
25
|
dependencies:
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: trollop
|
@@ -208,6 +208,7 @@ files:
|
|
208
208
|
- spec/logging_spec.rb
|
209
209
|
- spec/spec_helper.rb
|
210
210
|
- spec/support/matchers/capistrano_dsl_matchers.rb
|
211
|
+
- spec/support/matchers/exit_code_matches.rb
|
211
212
|
homepage: https://github.com/newrelic/centurion
|
212
213
|
licenses:
|
213
214
|
- MIT
|
@@ -249,3 +250,4 @@ test_files:
|
|
249
250
|
- spec/logging_spec.rb
|
250
251
|
- spec/spec_helper.rb
|
251
252
|
- spec/support/matchers/capistrano_dsl_matchers.rb
|
253
|
+
- spec/support/matchers/exit_code_matches.rb
|