centurion 1.0.10 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
- host 'docker-server-2.example.com'
103
+ host 'docker-server-2.example.com'
104
104
  end
105
105
 
106
106
  desc 'Staging environment'
107
107
  task :staging => :common do
108
- set_current_environment(:staging)
109
- env_vars YOUR_ENV: 'staging'
110
- env_vars MY_DB: 'radio-db.example.com'
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
- host_volume '/mnt/volume1', container_volume: '/mnt/volume2'
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
- set_current_environment(:production)
119
- env_vars YOUR_ENV: 'production'
120
- env_vars MY_DB: 'radio-db-prod.example.com'
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
- defines some host ports to map and sets which servers to deploy to. Some
131
- configuration will provided to the containers at startup time, in the form of
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
- All of the DSL items (`host_port`, `host_volume`, `env_vars`, `host`) can be
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 (#{possible_environments})", type: String, required: true, short: '-e'
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 :no_pull, 'Skip the pull_image step', type: :flag, default: false, long: '--no-pull'
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, 'https://registry.hub.docker.com/'
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[:no_pull]
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
 
@@ -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 { |k,v| "#{k}=#{v}" }
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)
@@ -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
- $stderr.puts "GET: #{path.inspect}"
16
+ uri = uri_for_repository_path(repository, path)
17
+ $stderr.puts "GET: #{uri}"
15
18
  response = Excon.get(
16
- @base_uri + path,
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
- $stderr.puts "GET: #{path.inspect}"
30
- response = Excon.get(@base_uri + path)
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
- JSON.load(response.body)
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
@@ -55,7 +55,7 @@ class Centurion::DockerViaCli
55
55
  validate_status(command)
56
56
  end
57
57
 
58
- def run_with_echo( command )
58
+ def run_with_echo(command)
59
59
  $stdout.sync = true
60
60
  $stderr.sync = true
61
61
  IO.popen(command) do |io|
@@ -1,3 +1,3 @@
1
1
  module Centurion
2
- VERSION = '1.0.10'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -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|
@@ -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
- it 'works with env_vars provided' do
148
- config = test_deploy.container_config_for(server, 'image_id', {}, 'FOO' => 'BAR')
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 Env ExposedPorts })
152
- expect(config['Env']).to eq(['FOO=BAR'])
157
+ expect(config.keys).to match_array(%w{ Hostname Image })
153
158
  end
154
159
 
155
- it 'works without env_vars or port_bindings' do
156
- config = test_deploy.container_config_for(server, 'image_id')
160
+ context 'when port bindings are specified' do
161
+ let(:port_bindings) { {1234 => 80, 9876 => 80} }
157
162
 
158
- expect(config).to be_a(Hash)
159
- expect(config.keys).to match_array(%w{ Hostname Image })
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
- it 'handles mapping host volumes' do
163
- config = test_deploy.container_config_for(server, 'image_id', nil, nil, ["/tmp/foo:/tmp/chaucer"])
171
+ context 'when env vars are specified' do
172
+ let(:env) { { 'FOO' => 'BAR', 'BAZ' => '%DOCKER_HOSTNAME%.example.com' } }
164
173
 
165
- expect(config).to be_a(Hash)
166
- expect(config.keys).to match_array(%w{ Hostname Image Volumes VolumesFrom })
167
- expect(config['Volumes']['/tmp/chaucer']).to eq({})
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
- it "exposes all ports" do
171
- config = test_deploy.container_config_for(server, 'image_id', {1234 => 80, 9876 => 80})
227
+ server.stub(:inspect_container)
172
228
 
173
- expect(config['ExposedPorts']).to be_a(Hash)
174
- expect(config['ExposedPorts'].keys).to eq [1234, 9876]
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({'Image'=>'image_id', 'Hostname'=>'host1', 'ExposedPorts'=>{'80/tcp'=>{}}})
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, nil, {}).once
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, {}, anything(), anything()
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.10
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-07-08 00:00:00.000000000 Z
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: '0'
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: '0'
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: 1.8.23
212
+ rubygems_version: 2.2.2
230
213
  signing_key:
231
- specification_version: 3
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