centurion 1.0.10 → 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 +7 -0
- data/CONTRIBUTORS.md +7 -0
- data/README.md +22 -14
- data/bin/centurion +4 -13
- data/centurion.gemspec +2 -2
- data/lib/centurion/deploy.rb +21 -13
- data/lib/centurion/deploy_dsl.rb +5 -1
- data/lib/centurion/docker_registry.rb +50 -7
- data/lib/centurion/docker_via_cli.rb +1 -1
- data/lib/centurion/version.rb +1 -1
- data/lib/tasks/deploy.rake +6 -3
- data/spec/deploy_dsl_spec.rb +6 -0
- data/spec/deploy_spec.rb +120 -33
- data/spec/docker_registry_spec.rb +59 -0
- metadata +28 -44
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3eb7f396991461f2a7e0db2fe83ff6209313233e
|
4
|
+
data.tar.gz: ee9f4bc48a899190fa48439353f5803523bbe2ec
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e0f35cd1e2626d5fd1c582ae2397c07b9daa7f53ace494094eea423f02865cba1d2852202e90cba8d7e82357002613db2ae48ee9e7280b11a6d53b329905b1f4
|
7
|
+
data.tar.gz: 01577cabe24211a9e65df8d0e4a140fd18042fab6742d5e65ed567becea161bc2582179b8c86df11055a69234b21f43e5b536c9f36a088bf79af0f139ad39905
|
data/CONTRIBUTORS.md
CHANGED
@@ -6,6 +6,10 @@ Post-release
|
|
6
6
|
|
7
7
|
Your name could be here!
|
8
8
|
|
9
|
+
* [Tom Dooner][tdooner]
|
10
|
+
* [Jonathan Chauncey][jchauncey]
|
11
|
+
* [Matt Sanford][mzsanford]
|
12
|
+
|
9
13
|
Pre-release
|
10
14
|
-----------
|
11
15
|
|
@@ -60,3 +64,6 @@ Contributor | Commits | Additions | Deletio
|
|
60
64
|
[intjonathan]: https://github.com/intjonathan
|
61
65
|
[duien]: https://github.com/duien
|
62
66
|
[frankywahl]: https://github.com/frankywahl
|
67
|
+
[tdooner]: https://github.com/tdooner
|
68
|
+
[jchauncey]: https://github.com/jchauncey
|
69
|
+
[mzsanford]: https://github.com/mzsanford
|
data/README.md
CHANGED
@@ -100,39 +100,47 @@ namespace :environment do
|
|
100
100
|
task :common do
|
101
101
|
set :image, 'example.com/newrelic/radio-radio'
|
102
102
|
host 'docker-server-1.example.com'
|
103
|
-
|
103
|
+
host 'docker-server-2.example.com'
|
104
104
|
end
|
105
105
|
|
106
106
|
desc 'Staging environment'
|
107
107
|
task :staging => :common do
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
set_current_environment(:staging)
|
109
|
+
env_vars YOUR_ENV: 'staging'
|
110
|
+
env_vars MY_DB: 'radio-db.example.com'
|
111
111
|
host_port 10234, container_port: 9292
|
112
112
|
host_port 10235, container_port: 9293
|
113
|
-
|
113
|
+
host_volume '/mnt/volume1', container_volume: '/mnt/volume2'
|
114
114
|
end
|
115
115
|
|
116
116
|
desc 'Production environment'
|
117
117
|
task :production => :common do
|
118
|
-
|
119
|
-
|
120
|
-
|
118
|
+
set_current_environment(:production)
|
119
|
+
env_vars YOUR_ENV: 'production'
|
120
|
+
env_vars MY_DB: 'radio-db-prod.example.com'
|
121
121
|
host_port 22234, container_port: 9292
|
122
122
|
host_port 23235, container_port: 9293
|
123
|
+
command ['/bin/bash', '-c', '/path/to/server -e production']
|
123
124
|
end
|
124
125
|
end
|
125
126
|
```
|
126
127
|
|
127
128
|
This sets up a staging and production environment and defines a `common` task
|
128
129
|
that will be run in either case. Note the dependency call in the task
|
129
|
-
definition for the `production` and `staging` tasks. Additionally, it
|
130
|
-
|
131
|
-
configuration will provided to the containers at startup time,
|
132
|
-
environment variables.
|
130
|
+
definition for the `production` and `staging` tasks. Additionally, it defines
|
131
|
+
some host ports to map, sets which servers to deploy to, and sets a custom
|
132
|
+
command. Some configuration will be provided to the containers at startup time,
|
133
|
+
in the form of environment variables.
|
133
134
|
|
134
|
-
|
135
|
-
specified more than once and will append to the configuration.
|
135
|
+
Most of the DSL items (`host_port`, `host_volume`, `env_vars`, `host`) can be
|
136
|
+
specified more than once and will append to the configuration. However, there
|
137
|
+
can only be one `command`; the last one will take priority.
|
138
|
+
|
139
|
+
###Interpolation
|
140
|
+
|
141
|
+
Currently there is one special string for interpolation that can be added to
|
142
|
+
any `env_var` value in the DSL. `%DOCKER_HOST%` will be replaced with the
|
143
|
+
current server's hostname in the environment variable at deployment time.
|
136
144
|
|
137
145
|
Deploying
|
138
146
|
---------
|
data/bin/centurion
CHANGED
@@ -16,11 +16,6 @@ Rake.application.options.trace = true
|
|
16
16
|
task_dir = File.expand_path(File.join(File.dirname(__FILE__), *%w{.. lib tasks}))
|
17
17
|
Dir.glob(File.join(task_dir, '*.rake')).each { |file| load file }
|
18
18
|
|
19
|
-
possible_environments = %w[development integration staging production local_integration]
|
20
|
-
def possible_environments.to_s
|
21
|
-
join(', ').sub(/, (\w+)$/, ', or \1')
|
22
|
-
end
|
23
|
-
|
24
19
|
#
|
25
20
|
# Trollop option setup
|
26
21
|
#
|
@@ -28,17 +23,13 @@ require 'trollop'
|
|
28
23
|
|
29
24
|
opts = Trollop::options do
|
30
25
|
opt :project, 'project (blog, forums...)', type: String, required: true, short: '-p'
|
31
|
-
opt :environment, "environment (
|
26
|
+
opt :environment, "environment (production, staging...)", type: String, required: true, short: '-e'
|
32
27
|
opt :action, 'action (deploy, list...)', type: String, default: 'list', short: '-a'
|
33
28
|
opt :image, 'image (yourco/project...)', type: String, required: false, short: '-i'
|
34
29
|
opt :tag, 'tag (latest...)', type: String, required: false, short: '-t'
|
35
30
|
opt :hosts, 'hosts, comma separated', type: String, required: false, short: '-h'
|
36
31
|
opt :docker_path, 'path to docker executable (default: docker)', type: String, default: 'docker', short: '-d'
|
37
|
-
opt :
|
38
|
-
end
|
39
|
-
|
40
|
-
unless possible_environments.include?(opts[:environment])
|
41
|
-
Trollop::die :environment, "is unknown; must be #{possible_environments}"
|
32
|
+
opt :nopull, 'Skip the pull_image step', type: :flag, default: false, long: '--no-pull'
|
42
33
|
end
|
43
34
|
|
44
35
|
set_current_environment(opts[:environment].to_sym)
|
@@ -64,11 +55,11 @@ set :hosts, opts[:hosts].split(",") if opts[:hosts]
|
|
64
55
|
|
65
56
|
# Default tag should be "latest"
|
66
57
|
set :tag, 'latest' unless any?(:tag)
|
67
|
-
set :docker_registry,
|
58
|
+
set :docker_registry, Centurion::DockerRegistry::OFFICIAL_URL unless any?(:docker_registry)
|
68
59
|
|
69
60
|
# Specify a path to docker executable
|
70
61
|
set :docker_path, opts[:docker_path]
|
71
62
|
|
72
|
-
set :no_pull, opts[:
|
63
|
+
set :no_pull, opts[:nopull]
|
73
64
|
|
74
65
|
invoke(opts[:action])
|
data/centurion.gemspec
CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
runs them on a fleet of hosts with the correct environment variables, host
|
23
23
|
mappings, and port mappings. Supports rolling deployments out of the box, and
|
24
24
|
makes it easy to ship applications to Docker servers.
|
25
|
-
|
25
|
+
|
26
26
|
We're using it to run our production infrastructure.
|
27
27
|
EOS
|
28
28
|
spec.homepage = 'https://github.com/newrelic/centurion'
|
@@ -39,7 +39,7 @@ Gem::Specification.new do |spec|
|
|
39
39
|
|
40
40
|
spec.add_development_dependency 'bundler'
|
41
41
|
spec.add_development_dependency 'rake'
|
42
|
-
spec.add_development_dependency 'rspec'
|
42
|
+
spec.add_development_dependency 'rspec', '~> 2.14.0'
|
43
43
|
spec.add_development_dependency 'pry'
|
44
44
|
spec.add_development_dependency 'simplecov'
|
45
45
|
|
data/lib/centurion/deploy.rb
CHANGED
@@ -67,7 +67,7 @@ module Centurion::Deploy
|
|
67
67
|
return false unless response
|
68
68
|
return true if response.status >= 200 && response.status < 300
|
69
69
|
|
70
|
-
warn "Got HTTP status: #{response.status}"
|
70
|
+
warn "Got HTTP status: #{response.status}"
|
71
71
|
false
|
72
72
|
end
|
73
73
|
|
@@ -87,12 +87,14 @@ module Centurion::Deploy
|
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
|
-
def container_config_for(target_server, image_id, port_bindings=nil, env_vars=nil, volumes=nil)
|
90
|
+
def container_config_for(target_server, image_id, port_bindings=nil, env_vars=nil, volumes=nil, command=nil)
|
91
91
|
container_config = {
|
92
92
|
'Image' => image_id,
|
93
93
|
'Hostname' => target_server.hostname,
|
94
94
|
}
|
95
95
|
|
96
|
+
container_config.merge!('Cmd' => command) if command
|
97
|
+
|
96
98
|
if port_bindings
|
97
99
|
container_config['ExposedPorts'] ||= {}
|
98
100
|
port_bindings.keys.each do |port|
|
@@ -101,7 +103,9 @@ module Centurion::Deploy
|
|
101
103
|
end
|
102
104
|
|
103
105
|
if env_vars
|
104
|
-
container_config['Env'] = env_vars.map
|
106
|
+
container_config['Env'] = env_vars.map do |k,v|
|
107
|
+
"#{k}=#{v.gsub('%DOCKER_HOSTNAME%', target_server.hostname)}"
|
108
|
+
end
|
105
109
|
end
|
106
110
|
|
107
111
|
if volumes
|
@@ -115,35 +119,39 @@ module Centurion::Deploy
|
|
115
119
|
container_config
|
116
120
|
end
|
117
121
|
|
118
|
-
def start_new_container(target_server, image_id, port_bindings, volumes, env_vars=nil)
|
119
|
-
container_config = container_config_for(target_server, image_id, port_bindings, env_vars, volumes)
|
120
|
-
start_container_with_config(target_server, volumes, port_bindings, container_config)
|
122
|
+
def start_new_container(target_server, image_id, port_bindings, volumes, env_vars=nil, command=nil, cidfile=nil)
|
123
|
+
container_config = container_config_for(target_server, image_id, port_bindings, env_vars, volumes, command)
|
124
|
+
start_container_with_config(target_server, volumes, port_bindings, container_config, cidfile)
|
121
125
|
end
|
122
126
|
|
123
|
-
def launch_console(target_server, image_id, port_bindings, volumes, env_vars=nil)
|
124
|
-
container_config = container_config_for(target_server, image_id, port_bindings, env_vars, volumes).merge(
|
125
|
-
'Cmd' => [ '/bin/bash' ],
|
127
|
+
def launch_console(target_server, image_id, port_bindings, volumes, env_vars=nil, cidfile=nil)
|
128
|
+
container_config = container_config_for(target_server, image_id, port_bindings, env_vars, volumes, ['/bin/bash']).merge(
|
126
129
|
'AttachStdin' => true,
|
127
130
|
'Tty' => true,
|
128
131
|
'OpenStdin' => true,
|
129
132
|
)
|
130
133
|
|
131
|
-
container = start_container_with_config(target_server, volumes, port_bindings, container_config)
|
134
|
+
container = start_container_with_config(target_server, volumes, port_bindings, container_config, cidfile)
|
132
135
|
|
133
136
|
target_server.attach(container['Id'])
|
134
137
|
end
|
135
138
|
|
136
139
|
private
|
137
|
-
|
138
|
-
def start_container_with_config(target_server, volumes, port_bindings, container_config)
|
140
|
+
|
141
|
+
def start_container_with_config(target_server, volumes, port_bindings, container_config, cidfile)
|
139
142
|
info "Creating new container for #{container_config['Image'][0..7]}"
|
140
143
|
new_container = target_server.create_container(container_config)
|
141
144
|
|
142
145
|
host_config = {}
|
146
|
+
|
143
147
|
# Map some host volumes if needed
|
144
148
|
host_config['Binds'] = volumes if volumes && !volumes.empty?
|
149
|
+
|
145
150
|
# Bind the ports
|
146
|
-
host_config['PortBindings'] = port_bindings
|
151
|
+
host_config['PortBindings'] = port_bindings
|
152
|
+
|
153
|
+
# Assign cidfile
|
154
|
+
host_config['ContainerIDFile'] = cidfile if cidfile
|
147
155
|
|
148
156
|
info "Starting new container #{new_container['Id'][0..7]}"
|
149
157
|
target_server.start_container(new_container['Id'], host_config)
|
data/lib/centurion/deploy_dsl.rb
CHANGED
@@ -22,6 +22,10 @@ module Centurion::DeployDSL
|
|
22
22
|
set(:hosts, current)
|
23
23
|
end
|
24
24
|
|
25
|
+
def command(command)
|
26
|
+
set(:command, command)
|
27
|
+
end
|
28
|
+
|
25
29
|
def localhost
|
26
30
|
# DOCKER_HOST is like 'tcp://127.0.0.1:4243'
|
27
31
|
docker_host_uri = URI.parse(ENV['DOCKER_HOST'] || "tcp://127.0.0.1")
|
@@ -34,7 +38,7 @@ module Centurion::DeployDSL
|
|
34
38
|
require_options_keys(options, [ :container_port ])
|
35
39
|
|
36
40
|
add_to_bindings(
|
37
|
-
options[:host_ip] || '0.0.0.0',
|
41
|
+
options[:host_ip] || '0.0.0.0',
|
38
42
|
options[:container_port],
|
39
43
|
port,
|
40
44
|
options[:type] || 'tcp'
|
@@ -5,15 +5,18 @@ require 'uri'
|
|
5
5
|
module Centurion; end
|
6
6
|
|
7
7
|
class Centurion::DockerRegistry
|
8
|
+
OFFICIAL_URL = 'https://registry.hub.docker.com'
|
9
|
+
|
8
10
|
def initialize(base_uri)
|
9
11
|
@base_uri = base_uri
|
10
12
|
end
|
11
|
-
|
13
|
+
|
12
14
|
def digest_for_tag(repository, tag)
|
13
15
|
path = "/v1/repositories/#{repository}/tags/#{tag}"
|
14
|
-
|
16
|
+
uri = uri_for_repository_path(repository, path)
|
17
|
+
$stderr.puts "GET: #{uri}"
|
15
18
|
response = Excon.get(
|
16
|
-
|
19
|
+
uri,
|
17
20
|
:headers => { "Content-Type" => "application/json" }
|
18
21
|
)
|
19
22
|
raise response.inspect unless response.status == 200
|
@@ -23,12 +26,52 @@ class Centurion::DockerRegistry
|
|
23
26
|
# refuses (possibly correctly) to handle
|
24
27
|
JSON.load('[' + response.body + ']').first
|
25
28
|
end
|
26
|
-
|
29
|
+
|
27
30
|
def repository_tags(repository)
|
28
31
|
path = "/v1/repositories/#{repository}/tags"
|
29
|
-
|
30
|
-
|
32
|
+
uri = uri_for_repository_path(repository, path)
|
33
|
+
$stderr.puts "GET: #{uri.inspect}"
|
34
|
+
response = Excon.get(uri)
|
31
35
|
raise response.inspect unless response.status == 200
|
32
|
-
|
36
|
+
|
37
|
+
tags = JSON.load(response.body)
|
38
|
+
|
39
|
+
# The Docker Registry API[1] specifies a result in the format
|
40
|
+
# { "[tag]" : "[image_id]" }. However, the official Docker registry returns a
|
41
|
+
# result like [{ "layer": "[image_id]", "name": "[tag]" }].
|
42
|
+
#
|
43
|
+
# So, we need to normalize the response to what the Docker Registry API
|
44
|
+
# specifies should be returned.
|
45
|
+
#
|
46
|
+
# [1]: https://docs.docker.com/v1.1/reference/api/registry_api/
|
47
|
+
|
48
|
+
if is_official_registry?(repository)
|
49
|
+
{}.tap do |hash|
|
50
|
+
tags.each do |tag|
|
51
|
+
hash[tag['name']] = tag['layer']
|
52
|
+
end
|
53
|
+
end
|
54
|
+
else
|
55
|
+
tags
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def is_official_registry?(repository)
|
62
|
+
if @base_uri == OFFICIAL_URL
|
63
|
+
return !repository.match(/^[a-z0-9-]+.[a-z]+\//)
|
64
|
+
end
|
65
|
+
false
|
66
|
+
end
|
67
|
+
|
68
|
+
def uri_for_repository_path(repository, path)
|
69
|
+
if repository.match(/\A([a-z0-9-]+.[a-z]+)\/(.*)\z/)
|
70
|
+
host = $1
|
71
|
+
short_image_name = $2
|
72
|
+
"https://#{host}#{path.gsub(repository, short_image_name)}"
|
73
|
+
else
|
74
|
+
@base_uri + path
|
75
|
+
end
|
33
76
|
end
|
34
77
|
end
|
data/lib/centurion/version.rb
CHANGED
data/lib/tasks/deploy.rake
CHANGED
@@ -51,7 +51,8 @@ namespace :deploy do
|
|
51
51
|
fetch(:image_id),
|
52
52
|
fetch(:port_bindings),
|
53
53
|
fetch(:binds),
|
54
|
-
fetch(:env_vars)
|
54
|
+
fetch(:env_vars),
|
55
|
+
fetch(:cidfile, '/etc/cidfile')
|
55
56
|
)
|
56
57
|
end
|
57
58
|
end
|
@@ -63,7 +64,8 @@ namespace :deploy do
|
|
63
64
|
fetch(:image_id),
|
64
65
|
fetch(:port_bindings),
|
65
66
|
fetch(:binds),
|
66
|
-
fetch(:env_vars)
|
67
|
+
fetch(:env_vars),
|
68
|
+
fetch(:cidfile, '/etc/cidfile')
|
67
69
|
)
|
68
70
|
end
|
69
71
|
end
|
@@ -77,7 +79,8 @@ namespace :deploy do
|
|
77
79
|
fetch(:image_id),
|
78
80
|
fetch(:port_bindings),
|
79
81
|
fetch(:binds),
|
80
|
-
fetch(:env_vars)
|
82
|
+
fetch(:env_vars),
|
83
|
+
fetch(:cidfile, '/etc/cidfile')
|
81
84
|
)
|
82
85
|
|
83
86
|
fetch(:port_bindings).each_pair do |container_port, host_ports|
|
data/spec/deploy_dsl_spec.rb
CHANGED
@@ -22,6 +22,12 @@ describe Centurion::DeployDSL do
|
|
22
22
|
DeployDSLTest.on_each_docker_host { |h| recipient.ping(h.hostname) }
|
23
23
|
end
|
24
24
|
|
25
|
+
it 'has a DSL method for specifying the start command' do
|
26
|
+
command = ['/bin/echo', 'hi']
|
27
|
+
DeployDSLTest.command command
|
28
|
+
expect(DeployDSLTest.fetch(:command)).to equal(command)
|
29
|
+
end
|
30
|
+
|
25
31
|
it 'adds new env_vars to the existing ones' do
|
26
32
|
DeployDSLTest.set(:env_vars, { 'SHAKESPEARE' => 'Hamlet' })
|
27
33
|
DeployDSLTest.env_vars('DICKENS' => 'David Copperfield')
|
data/spec/deploy_spec.rb
CHANGED
@@ -9,8 +9,8 @@ describe Centurion::Deploy do
|
|
9
9
|
let(:port) { 8484 }
|
10
10
|
let(:container) { { 'Ports' => [{ 'PublicPort' => port }, 'Created' => Time.now.to_i ], 'Id' => '21adfd2ef2ef2349494a', 'Names' => [ 'name1' ] } }
|
11
11
|
let(:endpoint) { '/status/check' }
|
12
|
-
let(:test_deploy) do
|
13
|
-
Object.new.tap do |o|
|
12
|
+
let(:test_deploy) do
|
13
|
+
Object.new.tap do |o|
|
14
14
|
o.send(:extend, Centurion::Deploy)
|
15
15
|
o.send(:extend, Centurion::DeployDSL)
|
16
16
|
o.send(:extend, Centurion::Logging)
|
@@ -84,7 +84,7 @@ describe Centurion::Deploy do
|
|
84
84
|
test_deploy.stub(:warn)
|
85
85
|
expect(test_deploy).to receive(:exit)
|
86
86
|
expect(test_deploy).to receive(:sleep).with(0)
|
87
|
-
|
87
|
+
|
88
88
|
test_deploy.wait_for_http_status_ok(server, port, '/foo', 'image_id', 'chaucer', 0, 1)
|
89
89
|
expect(test_deploy).to have_received(:info).with(/Waiting for the port/)
|
90
90
|
end
|
@@ -95,7 +95,7 @@ describe Centurion::Deploy do
|
|
95
95
|
test_deploy.stub(:error)
|
96
96
|
test_deploy.stub(:warn)
|
97
97
|
expect(test_deploy).to receive(:exit)
|
98
|
-
|
98
|
+
|
99
99
|
test_deploy.wait_for_http_status_ok(server, port, '/foo', 'image_id', 'chaucer', 1, 0)
|
100
100
|
expect(test_deploy).to have_received(:info).with(/Waiting for the port/)
|
101
101
|
end
|
@@ -144,91 +144,178 @@ describe Centurion::Deploy do
|
|
144
144
|
end
|
145
145
|
|
146
146
|
describe '#container_config_for' do
|
147
|
-
|
148
|
-
|
147
|
+
let(:image_id) { 'image_id' }
|
148
|
+
let(:port_bindings) { nil }
|
149
|
+
let(:env) { nil }
|
150
|
+
let(:volumes) { nil }
|
151
|
+
let(:command) { nil }
|
152
|
+
|
153
|
+
it 'works without env_vars, port_bindings, or a command' do
|
154
|
+
config = test_deploy.container_config_for(server, image_id)
|
149
155
|
|
150
156
|
expect(config).to be_a(Hash)
|
151
|
-
expect(config.keys).to match_array(%w{ Hostname Image
|
152
|
-
expect(config['Env']).to eq(['FOO=BAR'])
|
157
|
+
expect(config.keys).to match_array(%w{ Hostname Image })
|
153
158
|
end
|
154
159
|
|
155
|
-
|
156
|
-
|
160
|
+
context 'when port bindings are specified' do
|
161
|
+
let(:port_bindings) { {1234 => 80, 9876 => 80} }
|
157
162
|
|
158
|
-
|
159
|
-
|
163
|
+
it 'sets the ExposedPorts key in the config correctly' do
|
164
|
+
config = test_deploy.container_config_for(server, image_id, port_bindings)
|
165
|
+
|
166
|
+
expect(config['ExposedPorts']).to be_a(Hash)
|
167
|
+
expect(config['ExposedPorts'].keys).to eq port_bindings.keys
|
168
|
+
end
|
160
169
|
end
|
161
170
|
|
162
|
-
|
163
|
-
|
171
|
+
context 'when env vars are specified' do
|
172
|
+
let(:env) { { 'FOO' => 'BAR', 'BAZ' => '%DOCKER_HOSTNAME%.example.com' } }
|
164
173
|
|
165
|
-
|
166
|
-
|
167
|
-
|
174
|
+
it 'sets the Env key in the config' do
|
175
|
+
config = test_deploy.container_config_for(server, image_id, port_bindings, env)
|
176
|
+
|
177
|
+
expect(config).to be_a(Hash)
|
178
|
+
expect(config.keys).to match_array(%w{ Hostname Image Env })
|
179
|
+
expect(config['Env']).to include('FOO=BAR')
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'interpolates the hostname into env_vars' do
|
183
|
+
config = test_deploy.container_config_for(server, image_id, port_bindings, env)
|
184
|
+
|
185
|
+
expect(config['Env']).to include('BAZ=host1.example.com')
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context 'when volumes are specified' do
|
190
|
+
let(:volumes) { ["/tmp/foo:/tmp/chaucer"] }
|
191
|
+
|
192
|
+
it 'sets the Volumes key in the config' do
|
193
|
+
config = test_deploy.container_config_for(server, image_id, port_bindings, env, volumes)
|
194
|
+
|
195
|
+
expect(config).to be_a(Hash)
|
196
|
+
expect(config.keys).to match_array(%w{ Hostname Image Volumes VolumesFrom })
|
197
|
+
expect(config['Volumes']['/tmp/chaucer']).to eq({})
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'when a custom command is specified' do
|
202
|
+
let(:command) { ['/bin/echo', 'hi'] }
|
203
|
+
|
204
|
+
it 'sets the Cmd key in the config' do
|
205
|
+
config = test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command)
|
206
|
+
|
207
|
+
expect(config).to be_a(Hash)
|
208
|
+
expect(config.keys).to match_array(%w{ Hostname Image Cmd })
|
209
|
+
expect(config['Cmd']).to eq(command)
|
210
|
+
end
|
168
211
|
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe '#start_container_with_config' do
|
215
|
+
let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
|
216
|
+
|
217
|
+
it 'pass host_config to start_container' do
|
218
|
+
server.stub(:container_config_for).and_return({
|
219
|
+
'Image' => 'image_id',
|
220
|
+
'Hostname' => server.hostname,
|
221
|
+
})
|
222
|
+
|
223
|
+
server.stub(:create_container).and_return({
|
224
|
+
'Id' => 'abc123456'
|
225
|
+
})
|
169
226
|
|
170
|
-
|
171
|
-
config = test_deploy.container_config_for(server, 'image_id', {1234 => 80, 9876 => 80})
|
227
|
+
server.stub(:inspect_container)
|
172
228
|
|
173
|
-
expect(
|
174
|
-
|
229
|
+
expect(server).to receive(:start_container).with(
|
230
|
+
'abc123456',
|
231
|
+
{
|
232
|
+
'PortBindings' => bindings,
|
233
|
+
'ContainerIDFile' => '/etc/cidfile'
|
234
|
+
}
|
235
|
+
).once
|
236
|
+
|
237
|
+
test_deploy.start_new_container(server, 'image_id', bindings, {}, nil, nil, '/etc/cidfile')
|
175
238
|
end
|
176
239
|
end
|
177
240
|
|
178
241
|
describe '#start_new_container' do
|
179
242
|
let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
|
243
|
+
let(:env) { { 'FOO' => 'BAR' } }
|
244
|
+
let(:volumes) { ['/foo:/bar'] }
|
245
|
+
let(:command) { ['/bin/echo', 'hi'] }
|
180
246
|
|
181
247
|
it 'configures the container' do
|
182
|
-
expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, nil, {}).once
|
248
|
+
expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, nil, {}, nil).once
|
249
|
+
|
183
250
|
test_deploy.stub(:start_container_with_config)
|
184
251
|
|
185
|
-
test_deploy.start_new_container(server, 'image_id', bindings, {})
|
252
|
+
test_deploy.start_new_container(server, 'image_id', bindings, {}, nil)
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'pass cidfile to start_container_with_config method' do
|
256
|
+
test_deploy.stub(:container_config_for)
|
257
|
+
|
258
|
+
expect(test_deploy).to receive(:start_container_with_config).with(server, anything(), bindings, nil, '/etc/cidfile').once
|
259
|
+
|
260
|
+
test_deploy.start_new_container(server, 'image_id', bindings, {}, nil, nil, '/etc/cidfile')
|
186
261
|
end
|
187
262
|
|
188
263
|
it 'starts the container' do
|
189
|
-
expect(test_deploy).to receive(:start_container_with_config).with(server, {}, anything(), anything())
|
264
|
+
expect(test_deploy).to receive(:start_container_with_config).with(server, {}, anything(), anything(), anything())
|
190
265
|
|
191
266
|
test_deploy.start_new_container(server, 'image_id', bindings, {})
|
192
267
|
end
|
193
268
|
|
194
269
|
it 'ultimately asks the server object to do the work' do
|
195
270
|
server.should_receive(:create_container).with(
|
196
|
-
hash_including(
|
271
|
+
hash_including(
|
272
|
+
'Image'=>'image_id',
|
273
|
+
'Hostname'=>'host1',
|
274
|
+
'ExposedPorts'=>{'80/tcp'=>{}},
|
275
|
+
'Cmd' => command,
|
276
|
+
'Env' => ['FOO=BAR'],
|
277
|
+
'Volumes' => {'/bar' => {}},
|
278
|
+
)
|
197
279
|
).and_return(container)
|
198
280
|
|
199
281
|
server.should_receive(:start_container)
|
200
282
|
server.should_receive(:inspect_container)
|
201
283
|
|
202
|
-
new_container = test_deploy.start_new_container(server, 'image_id', bindings,
|
284
|
+
new_container = test_deploy.start_new_container(server, 'image_id', bindings, volumes, env, command)
|
203
285
|
expect(new_container).to eq(container)
|
204
286
|
end
|
205
287
|
end
|
206
288
|
|
207
289
|
describe '#launch_console' do
|
208
290
|
let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
|
291
|
+
let(:volumes) { nil }
|
292
|
+
let(:env) { nil }
|
293
|
+
let(:command) { nil }
|
294
|
+
let(:cidfile) { nil }
|
209
295
|
|
210
296
|
it 'configures the container' do
|
211
|
-
expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings,
|
297
|
+
expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, env, volumes, command).once
|
212
298
|
test_deploy.stub(:start_container_with_config)
|
213
299
|
|
214
|
-
test_deploy.start_new_container(server, 'image_id', bindings,
|
300
|
+
test_deploy.start_new_container(server, 'image_id', bindings, volumes, env, command, cidfile)
|
215
301
|
end
|
216
302
|
|
217
303
|
it 'augments the container_config' do
|
218
|
-
expect(test_deploy).to receive(:start_container_with_config).with(server,
|
304
|
+
expect(test_deploy).to receive(:start_container_with_config).with(server, volumes,
|
219
305
|
anything(),
|
220
|
-
hash_including('Cmd' => [ '/bin/bash' ], 'AttachStdin' => true , 'Tty' => true , 'OpenStdin' => true)
|
306
|
+
hash_including('Cmd' => [ '/bin/bash' ], 'AttachStdin' => true , 'Tty' => true , 'OpenStdin' => true),
|
307
|
+
anything()
|
221
308
|
).and_return({'Id' => 'shakespeare'})
|
222
309
|
|
223
|
-
test_deploy.launch_console(server, 'image_id', bindings,
|
310
|
+
test_deploy.launch_console(server, 'image_id', bindings, volumes, env)
|
224
311
|
end
|
225
312
|
|
226
313
|
it 'starts the console' do
|
227
314
|
expect(test_deploy).to receive(:start_container_with_config).with(
|
228
|
-
server,
|
315
|
+
server, nil, anything(), anything(), anything()
|
229
316
|
).and_return({'Id' => 'shakespeare'})
|
230
317
|
|
231
|
-
test_deploy.launch_console(server, 'image_id', bindings,
|
318
|
+
test_deploy.launch_console(server, 'image_id', bindings, volumes, env)
|
232
319
|
expect(server).to have_received(:attach).with('shakespeare')
|
233
320
|
end
|
234
321
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'centurion/docker_registry'
|
3
|
+
|
4
|
+
describe Centurion::DockerRegistry do
|
5
|
+
let(:registry_url) { 'http://localhost/' }
|
6
|
+
let(:registry) { Centurion::DockerRegistry.new(registry_url) }
|
7
|
+
|
8
|
+
describe '#repository_tags' do
|
9
|
+
let(:repository) { 'foobar' }
|
10
|
+
let(:tag_name) { 'arbitrary_tag' }
|
11
|
+
let(:image_id) { 'deadbeef0000' }
|
12
|
+
let(:url) { any_args() }
|
13
|
+
|
14
|
+
before do
|
15
|
+
expect(Excon).to receive(:get).
|
16
|
+
with(url).
|
17
|
+
and_return(double(status: 200, body: response))
|
18
|
+
end
|
19
|
+
|
20
|
+
subject { registry.repository_tags(repository) }
|
21
|
+
|
22
|
+
context 'when given a response from the official Docker registry' do
|
23
|
+
let(:registry_url) { Centurion::DockerRegistry::OFFICIAL_URL }
|
24
|
+
let(:response) { <<-JSON.strip }
|
25
|
+
[{"layer": "#{image_id}", "name": "#{tag_name}"}]
|
26
|
+
JSON
|
27
|
+
|
28
|
+
it 'normalizes the response' do
|
29
|
+
expect(subject).to eq(tag_name => image_id)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when given a response from the open-source Docker registry' do
|
34
|
+
let(:response) { <<-JSON.strip }
|
35
|
+
{"#{tag_name}": "#{image_id}"}
|
36
|
+
JSON
|
37
|
+
|
38
|
+
it 'normalizes the response' do
|
39
|
+
expect(subject).to eq(tag_name => image_id)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when given the official Docker registry and a repository with a host name' do
|
44
|
+
let(:registry_url) { Centurion::DockerRegistry::OFFICIAL_URL }
|
45
|
+
let(:repository) { 'example.com/foobar' }
|
46
|
+
|
47
|
+
let(:response) { <<-JSON.strip }
|
48
|
+
{"#{tag_name}": "#{image_id}"}
|
49
|
+
JSON
|
50
|
+
|
51
|
+
let(:url) { 'https://example.com/v1/repositories/foobar/tags' }
|
52
|
+
|
53
|
+
it 'fetches from the image-referenced registry' do
|
54
|
+
expect(subject).to eq(tag_name => image_id)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: centurion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Nic Benders
|
@@ -21,134 +20,118 @@ authors:
|
|
21
20
|
autorequire:
|
22
21
|
bindir: bin
|
23
22
|
cert_chain: []
|
24
|
-
date: 2014-
|
23
|
+
date: 2014-09-08 00:00:00.000000000 Z
|
25
24
|
dependencies:
|
26
25
|
- !ruby/object:Gem::Dependency
|
27
26
|
name: trollop
|
28
27
|
requirement: !ruby/object:Gem::Requirement
|
29
|
-
none: false
|
30
28
|
requirements:
|
31
|
-
- -
|
29
|
+
- - ">="
|
32
30
|
- !ruby/object:Gem::Version
|
33
31
|
version: '0'
|
34
32
|
type: :runtime
|
35
33
|
prerelease: false
|
36
34
|
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
none: false
|
38
35
|
requirements:
|
39
|
-
- -
|
36
|
+
- - ">="
|
40
37
|
- !ruby/object:Gem::Version
|
41
38
|
version: '0'
|
42
39
|
- !ruby/object:Gem::Dependency
|
43
40
|
name: excon
|
44
41
|
requirement: !ruby/object:Gem::Requirement
|
45
|
-
none: false
|
46
42
|
requirements:
|
47
|
-
- - ~>
|
43
|
+
- - "~>"
|
48
44
|
- !ruby/object:Gem::Version
|
49
45
|
version: '0.33'
|
50
46
|
type: :runtime
|
51
47
|
prerelease: false
|
52
48
|
version_requirements: !ruby/object:Gem::Requirement
|
53
|
-
none: false
|
54
49
|
requirements:
|
55
|
-
- - ~>
|
50
|
+
- - "~>"
|
56
51
|
- !ruby/object:Gem::Version
|
57
52
|
version: '0.33'
|
58
53
|
- !ruby/object:Gem::Dependency
|
59
54
|
name: logger-colors
|
60
55
|
requirement: !ruby/object:Gem::Requirement
|
61
|
-
none: false
|
62
56
|
requirements:
|
63
|
-
- -
|
57
|
+
- - ">="
|
64
58
|
- !ruby/object:Gem::Version
|
65
59
|
version: '0'
|
66
60
|
type: :runtime
|
67
61
|
prerelease: false
|
68
62
|
version_requirements: !ruby/object:Gem::Requirement
|
69
|
-
none: false
|
70
63
|
requirements:
|
71
|
-
- -
|
64
|
+
- - ">="
|
72
65
|
- !ruby/object:Gem::Version
|
73
66
|
version: '0'
|
74
67
|
- !ruby/object:Gem::Dependency
|
75
68
|
name: bundler
|
76
69
|
requirement: !ruby/object:Gem::Requirement
|
77
|
-
none: false
|
78
70
|
requirements:
|
79
|
-
- -
|
71
|
+
- - ">="
|
80
72
|
- !ruby/object:Gem::Version
|
81
73
|
version: '0'
|
82
74
|
type: :development
|
83
75
|
prerelease: false
|
84
76
|
version_requirements: !ruby/object:Gem::Requirement
|
85
|
-
none: false
|
86
77
|
requirements:
|
87
|
-
- -
|
78
|
+
- - ">="
|
88
79
|
- !ruby/object:Gem::Version
|
89
80
|
version: '0'
|
90
81
|
- !ruby/object:Gem::Dependency
|
91
82
|
name: rake
|
92
83
|
requirement: !ruby/object:Gem::Requirement
|
93
|
-
none: false
|
94
84
|
requirements:
|
95
|
-
- -
|
85
|
+
- - ">="
|
96
86
|
- !ruby/object:Gem::Version
|
97
87
|
version: '0'
|
98
88
|
type: :development
|
99
89
|
prerelease: false
|
100
90
|
version_requirements: !ruby/object:Gem::Requirement
|
101
|
-
none: false
|
102
91
|
requirements:
|
103
|
-
- -
|
92
|
+
- - ">="
|
104
93
|
- !ruby/object:Gem::Version
|
105
94
|
version: '0'
|
106
95
|
- !ruby/object:Gem::Dependency
|
107
96
|
name: rspec
|
108
97
|
requirement: !ruby/object:Gem::Requirement
|
109
|
-
none: false
|
110
98
|
requirements:
|
111
|
-
- -
|
99
|
+
- - "~>"
|
112
100
|
- !ruby/object:Gem::Version
|
113
|
-
version:
|
101
|
+
version: 2.14.0
|
114
102
|
type: :development
|
115
103
|
prerelease: false
|
116
104
|
version_requirements: !ruby/object:Gem::Requirement
|
117
|
-
none: false
|
118
105
|
requirements:
|
119
|
-
- -
|
106
|
+
- - "~>"
|
120
107
|
- !ruby/object:Gem::Version
|
121
|
-
version:
|
108
|
+
version: 2.14.0
|
122
109
|
- !ruby/object:Gem::Dependency
|
123
110
|
name: pry
|
124
111
|
requirement: !ruby/object:Gem::Requirement
|
125
|
-
none: false
|
126
112
|
requirements:
|
127
|
-
- -
|
113
|
+
- - ">="
|
128
114
|
- !ruby/object:Gem::Version
|
129
115
|
version: '0'
|
130
116
|
type: :development
|
131
117
|
prerelease: false
|
132
118
|
version_requirements: !ruby/object:Gem::Requirement
|
133
|
-
none: false
|
134
119
|
requirements:
|
135
|
-
- -
|
120
|
+
- - ">="
|
136
121
|
- !ruby/object:Gem::Version
|
137
122
|
version: '0'
|
138
123
|
- !ruby/object:Gem::Dependency
|
139
124
|
name: simplecov
|
140
125
|
requirement: !ruby/object:Gem::Requirement
|
141
|
-
none: false
|
142
126
|
requirements:
|
143
|
-
- -
|
127
|
+
- - ">="
|
144
128
|
- !ruby/object:Gem::Version
|
145
129
|
version: '0'
|
146
130
|
type: :development
|
147
131
|
prerelease: false
|
148
132
|
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
none: false
|
150
133
|
requirements:
|
151
|
-
- -
|
134
|
+
- - ">="
|
152
135
|
- !ruby/object:Gem::Version
|
153
136
|
version: '0'
|
154
137
|
description:
|
@@ -172,7 +155,7 @@ executables:
|
|
172
155
|
extensions: []
|
173
156
|
extra_rdoc_files: []
|
174
157
|
files:
|
175
|
-
- .gitignore
|
158
|
+
- ".gitignore"
|
176
159
|
- CONTRIBUTORS.md
|
177
160
|
- Gemfile
|
178
161
|
- LICENSE
|
@@ -198,6 +181,7 @@ files:
|
|
198
181
|
- spec/capistrano_dsl_spec.rb
|
199
182
|
- spec/deploy_dsl_spec.rb
|
200
183
|
- spec/deploy_spec.rb
|
184
|
+
- spec/docker_registry_spec.rb
|
201
185
|
- spec/docker_server_group_spec.rb
|
202
186
|
- spec/docker_server_spec.rb
|
203
187
|
- spec/docker_via_api_spec.rb
|
@@ -208,27 +192,26 @@ files:
|
|
208
192
|
homepage: https://github.com/newrelic/centurion
|
209
193
|
licenses:
|
210
194
|
- MIT
|
195
|
+
metadata: {}
|
211
196
|
post_install_message:
|
212
197
|
rdoc_options: []
|
213
198
|
require_paths:
|
214
199
|
- lib
|
215
200
|
required_ruby_version: !ruby/object:Gem::Requirement
|
216
|
-
none: false
|
217
201
|
requirements:
|
218
|
-
- -
|
202
|
+
- - ">="
|
219
203
|
- !ruby/object:Gem::Version
|
220
204
|
version: 1.9.3
|
221
205
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
222
|
-
none: false
|
223
206
|
requirements:
|
224
|
-
- -
|
207
|
+
- - ">="
|
225
208
|
- !ruby/object:Gem::Version
|
226
209
|
version: '0'
|
227
210
|
requirements: []
|
228
211
|
rubyforge_project:
|
229
|
-
rubygems_version:
|
212
|
+
rubygems_version: 2.2.2
|
230
213
|
signing_key:
|
231
|
-
specification_version:
|
214
|
+
specification_version: 4
|
232
215
|
summary: A deployment tool for Docker. Takes containers from a Docker registry and
|
233
216
|
runs them on a fleet of hosts with the correct environment variables, host mappings,
|
234
217
|
and port mappings. Supports rolling deployments out of the box, and makes it easy
|
@@ -237,6 +220,7 @@ test_files:
|
|
237
220
|
- spec/capistrano_dsl_spec.rb
|
238
221
|
- spec/deploy_dsl_spec.rb
|
239
222
|
- spec/deploy_spec.rb
|
223
|
+
- spec/docker_registry_spec.rb
|
240
224
|
- spec/docker_server_group_spec.rb
|
241
225
|
- spec/docker_server_spec.rb
|
242
226
|
- spec/docker_via_api_spec.rb
|