chef-provisioning-docker 1.0.0.beta.1 → 1.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  require 'chef/provisioning/transport'
2
2
  require 'chef/provisioning/transport/ssh'
3
+ require 'chef/provisioning/docker_driver/chef_zero_http_proxy'
3
4
  require 'docker'
4
5
  require 'archive/tar/minitar'
5
6
  require 'shellwords'
@@ -21,11 +22,12 @@ module DockerDriver
21
22
  attr_reader :config
22
23
  attr_accessor :container
23
24
 
24
- def execute(command, options={})
25
+ def execute(command, timeout: nil, keep_stdin_open: nil, tty: nil, detached: nil, **options)
25
26
  opts = {}
26
- if options[:keep_stdin_open]
27
- opts[:stdin] = true
28
- end
27
+ opts[:tty] = tty unless tty.nil?
28
+ opts[:detached] = detached unless detached.nil?
29
+ opts[:stdin] = keep_stdin_open unless keep_stdin_open.nil?
30
+ opts[:wait] = timeout unless timeout.nil?
29
31
 
30
32
  command = Shellwords.split(command) if command.is_a?(String)
31
33
  Chef::Log.debug("execute #{command.inspect} on container #{container.id} with options #{opts}'")
@@ -115,8 +117,34 @@ module DockerDriver
115
117
  Chef::Log.debug("Session loop completed normally")
116
118
  end
117
119
  else
118
- # We are the host. The docker machine was run with --net=host, so it
119
- # will be able to talk to us automatically.
120
+ # We are the host. Find the docker machine's gateway (us) and talk to that;
121
+ # and set up a little proxy that will forward the container's requests to
122
+ # chef-zero
123
+ result = execute('ip route list', :read_only => true)
124
+
125
+ Chef::Log.debug("IP route: #{result.stdout}")
126
+
127
+ if result.stdout =~ /default via (\S+)/
128
+
129
+ old_uri = uri.dup
130
+
131
+ uri.host = $1
132
+
133
+ if !@proxy_thread
134
+ # Listen to docker instances only, and forward to localhost
135
+ @proxy_thread = Thread.new do
136
+ begin
137
+ Chef::Log.debug("Starting proxy thread: #{old_uri.host}:#{old_uri.port} <--> #{uri.host}:#{uri.port}")
138
+ ChefZeroHttpProxy.new(uri.host, uri.port, old_uri.host, old_uri.port).run
139
+ rescue
140
+ Chef::Log.error("Proxy thread unable to start: #{$!}")
141
+ end
142
+ end
143
+ end
144
+ else
145
+ raise "Cannot forward port: ip route ls did not show default in expected format.\nSTDOUT: #{result.stdout}"
146
+ end
147
+
120
148
  end
121
149
  else
122
150
  old_uri = uri.dup
@@ -140,8 +168,6 @@ module DockerDriver
140
168
  def available?
141
169
  end
142
170
 
143
- private
144
-
145
171
  def is_local_machine(host)
146
172
  local_addrs = Socket.ip_address_list
147
173
  host_addrs = Addrinfo.getaddrinfo(host, nil)
@@ -152,7 +178,7 @@ module DockerDriver
152
178
  end
153
179
  end
154
180
 
155
- def docker_toolkit_transport
181
+ def docker_toolkit_transport(connection_url=nil)
156
182
  if !defined?(@docker_toolkit_transport)
157
183
  # Figure out which docker-machine this container is in
158
184
  begin
@@ -162,18 +188,21 @@ module DockerDriver
162
188
  @docker_toolkit_transport = nil
163
189
  return
164
190
  end
191
+
192
+ connection_url ||= container.connection.url
193
+
165
194
  Chef::Log.debug("Found docker machines:")
166
195
  docker_machine = nil
167
196
  docker_machines.lines.each do |line|
168
197
  machine_name, machine_url = line.chomp.split(',', 2)
169
198
  Chef::Log.debug("- #{machine_name} at URL #{machine_url.inspect}")
170
- if machine_url == container.connection.url
171
- Chef::Log.debug("Docker machine #{machine_name} at URL #{machine_url} matches the container's URL #{container.connection.url}! Will use it for port forwarding.")
199
+ if machine_url == connection_url
200
+ Chef::Log.debug("Docker machine #{machine_name} at URL #{machine_url} matches the container's URL #{connection_url}! Will use it for port forwarding.")
172
201
  docker_machine = machine_name
173
202
  end
174
203
  end
175
204
  if !docker_machine
176
- Chef::Log.debug("Docker Toolkit is installed, but no Docker machine's URL matches #{container.connection.url.inspect}. Assuming docker must be installed as well ...")
205
+ Chef::Log.debug("Docker Toolkit is installed, but no Docker machine's URL matches #{connection_url.inspect}. Assuming docker must be installed as well ...")
177
206
  @docker_toolkit_transport = nil
178
207
  return
179
208
  end
@@ -28,6 +28,10 @@ module DockerDriver
28
28
  Driver.new(driver_url, config)
29
29
  end
30
30
 
31
+ def driver_url
32
+ "docker:#{Docker.url}"
33
+ end
34
+
31
35
  def initialize(driver_url, config)
32
36
  super
33
37
  url = Driver.connection_url(driver_url)
@@ -37,10 +41,14 @@ module DockerDriver
37
41
  # to be set for command-line utilities
38
42
  ENV['DOCKER_HOST'] = url
39
43
  Chef::Log.debug("Setting Docker URL to #{url}")
40
- Docker.url = url
41
44
  end
42
45
 
43
- @connection = Docker.connection
46
+ ENV['DOCKER_HOST'] ||= url if url
47
+ Docker.logger = Chef::Log
48
+ options = Docker.options.dup || {}
49
+ options.merge!(read_timeout: 600)
50
+ options.merge!(config.hash_dup) if config
51
+ @connection = Docker::Connection.new(url || Docker.url, options)
44
52
  end
45
53
 
46
54
  def self.canonicalize_url(driver_url, config)
@@ -75,6 +83,8 @@ module DockerDriver
75
83
  action_handler,
76
84
  machine_spec
77
85
  )
86
+
87
+ # Grab options from existing machine (TODO seems wrong) and set the machine_spec to that
78
88
  docker_options = machine_options[:docker_options]
79
89
  container_id = nil
80
90
  image_id = machine_options[:image_id]
@@ -84,7 +94,6 @@ module DockerDriver
84
94
  image_id ||= machine_spec.reference['image_id']
85
95
  docker_options ||= machine_spec.reference['docker_options']
86
96
  end
87
-
88
97
  container_name ||= machine_spec.name
89
98
  machine_spec.reference = {
90
99
  'driver_url' => driver_url,
@@ -96,125 +105,66 @@ module DockerDriver
96
105
  'docker_options' => stringize_keys(docker_options),
97
106
  'container_id' => container_id
98
107
  }
99
- build_container(machine_spec, docker_options)
100
108
  end
101
109
 
102
110
  def ready_machine(action_handler, machine_spec, machine_options)
103
- start_machine(action_handler, machine_spec, machine_options)
104
111
  machine_for(machine_spec, machine_options)
105
112
  end
106
113
 
107
- def build_container(machine_spec, docker_options)
114
+ def start_machine(action_handler, machine_spec, machine_options)
108
115
  container = container_for(machine_spec)
109
- return container unless container.nil?
110
-
111
- image = find_image(machine_spec) ||
112
- build_image(machine_spec, docker_options)
113
-
114
- args = [
115
- 'docker',
116
- 'run',
117
- '--name',
118
- machine_spec.reference['container_name'],
119
- '--detach'
120
- ]
121
-
122
- if docker_options[:keep_stdin_open]
123
- args << '-i'
124
- end
125
-
126
- # We create the initial container with --net host so it can access things
127
- # while it converges. When the final container starts, it will have its
128
- # normal network.
129
- args << '--net'
130
- args << 'host'
131
-
132
- if docker_options[:env]
133
- docker_options[:env].each do |key, value|
134
- args << '-e'
135
- args << "#{key}=#{value}"
116
+ if container && !container.info['State']['Running']
117
+ action_handler.perform_action "start container #{machine_spec.name}" do
118
+ container.start!
136
119
  end
137
120
  end
121
+ end
138
122
 
139
- if docker_options[:ports]
140
- docker_options[:ports].each do |portnum|
141
- args << '-p'
142
- args << "#{portnum}"
143
- end
144
- end
123
+ # Connect to machine without acquiring it
124
+ def connect_to_machine(machine_spec, machine_options)
125
+ Chef::Log.debug('Connect to machine')
126
+ machine_for(machine_spec, machine_options)
127
+ end
145
128
 
146
- if docker_options[:volumes]
147
- docker_options[:volumes].each do |volume|
148
- args << '-v'
149
- args << "#{volume}"
129
+ def destroy_machine(action_handler, machine_spec, machine_options)
130
+ container = container_for(machine_spec)
131
+ if container
132
+ image_id = container.info['Image']
133
+ action_handler.perform_action "stop and destroy container #{machine_spec.name}" do
134
+ container.stop
135
+ container.delete
150
136
  end
151
137
  end
138
+ end
152
139
 
153
- if docker_options[:dns]
154
- docker_options[:dns].each do |entry|
155
- args << '--dns'
156
- args << "#{entry}"
140
+ def stop_machine(action_handler, machine_spec, machine_options)
141
+ container = container_for(machine_spec)
142
+ if container.info['State']['Running']
143
+ action_handler.perform_action "stop container #{machine_spec.name}" do
144
+ container.stop!
157
145
  end
158
146
  end
159
-
160
- if docker_options[:dns_search]
161
- args << '--dns-search'
162
- args << "#{docker_options[:dns_search]}"
163
- end
164
-
165
- args << image.id
166
- args += Shellwords.split("/bin/sh -c 'while true;do sleep 1000; done'")
167
-
168
- cmdstr = Shellwords.join(args)
169
- Chef::Log.debug("Executing #{cmdstr}")
170
-
171
- cmd = Mixlib::ShellOut.new(cmdstr)
172
- cmd.run_command
173
-
174
- container = Docker::Container.get(machine_spec.reference['container_name'])
175
-
176
- Chef::Log.debug("Container id: #{container.id}")
177
- machine_spec.reference['container_id'] = container.id
178
- container
179
147
  end
180
148
 
181
- def build_image(machine_spec, docker_options)
182
- base_image = docker_options[:base_image] || base_image_for(machine_spec)
183
- source_name = base_image[:name]
184
- source_repository = base_image[:repository]
185
- source_tag = base_image[:tag]
186
-
187
- target_tag = machine_spec.reference['container_name']
188
-
189
- image = Docker::Image.create(
190
- 'fromImage' => source_name,
191
- 'repo' => source_repository,
192
- 'tag' => source_tag
193
- )
194
-
195
- Chef::Log.debug("Allocated #{image}")
196
- image.tag('repo' => 'chef', 'tag' => target_tag)
197
- Chef::Log.debug("Tagged image #{image}")
198
-
199
- machine_spec.reference['image_id'] = image.id
200
- image
201
- end
149
+ #
150
+ # Images
151
+ #
202
152
 
203
153
  def allocate_image(action_handler, image_spec, image_options, machine_spec, machine_options)
154
+ tag_container_image(action_handler, machine_spec, image_spec)
155
+
204
156
  # Set machine options on the image to match our newly created image
205
157
  image_spec.reference = {
206
158
  'driver_url' => driver_url,
207
159
  'driver_version' => Chef::Provisioning::DockerDriver::VERSION,
208
160
  'allocated_at' => Time.now.to_i,
209
- :docker_options => {
210
- :base_image => {
211
- :name => "chef_#{image_spec.name}",
212
- :repository => 'chef',
213
- :tag => image_spec.name
214
- },
215
- :from_image => true
161
+ 'docker_options' => {
162
+ 'base_image' => {
163
+ 'name' => image_spec.name
164
+ }
216
165
  }
217
166
  }
167
+
218
168
  # Workaround for chef/chef-provisioning-docker#37
219
169
  machine_spec.attrs[:keep_image] = true
220
170
  end
@@ -225,66 +175,33 @@ module DockerDriver
225
175
 
226
176
  # workaround for https://github.com/chef/chef-provisioning/issues/358.
227
177
  def destroy_image(action_handler, image_spec, image_options, machine_options={})
228
- image = Docker::Image.get("chef:#{image_spec.name}")
178
+ image = image_for(image_spec)
229
179
  image.delete unless image.nil?
230
180
  end
231
181
 
232
- # Connect to machine without acquiring it
233
- def connect_to_machine(machine_spec, machine_options)
234
- Chef::Log.debug('Connect to machine')
235
- machine_for(machine_spec, machine_options)
236
- end
182
+ private
237
183
 
238
- def destroy_machine(action_handler, machine_spec, machine_options)
184
+ def tag_container_image(action_handler, machine_spec, image_spec)
239
185
  container = container_for(machine_spec)
240
- if container
241
- Chef::Log.debug("Destroying container: #{container.id}")
242
- container.delete(:force => true)
243
- end
244
-
245
- if !machine_spec.attrs[:keep_image] && !machine_options[:keep_image]
246
- image = find_image(machine_spec)
247
- Chef::Log.debug("Destroying image: chef:#{image.id}")
248
- image.delete
186
+ existing_image = image_for(image_spec)
187
+ unless existing_image && existing_image.id == container.info['Image']
188
+ image = Docker::Image.get(container.info['Image'], {}, @connection)
189
+ action_handler.perform_action "tag image #{container.info['Image']} as chef-images/#{image_spec.name}" do
190
+ image.tag('repo' => image_spec.name, 'force' => true)
191
+ end
249
192
  end
250
193
  end
251
194
 
252
- def stop_machine(action_handler, machine_spec, machine_options)
253
- container = container_for(machine_spec)
254
- return if container.nil?
255
-
256
- container.stop if container.info['State']['Running']
195
+ def to_camel_case(name)
196
+ name.split('_').map { |x| x.capitalize }.join("")
257
197
  end
258
198
 
259
- def find_image(machine_spec)
260
- image = nil
261
-
262
- if machine_spec.reference['image_id']
263
- begin
264
- image = Docker::Image.get(machine_spec.reference['image_id'])
265
- rescue Docker::Error::NotFoundError
266
- end
267
- end
268
-
269
- if image.nil?
270
- image_name = "chef:#{machine_spec.reference['container_name']}"
271
- if machine_spec.from_image
272
- base_image = base_image_for(machine_spec)
273
- image_name = "#{base_image[:repository]}:#{base_image[:tag]}"
274
- end
275
-
276
- image = Docker::Image.all.select {
277
- |i| i.info['RepoTags'].include? image_name
278
- }.first
279
-
280
- if machine_spec.from_image && image.nil?
281
- raise "Unable to locate machine_image for #{image_name}"
282
- end
283
- end
284
-
285
- machine_spec.reference['image_id'] = image.id if image
286
-
287
- image
199
+ def to_snake_case(name)
200
+ # ExposedPorts -> _exposed_ports
201
+ name = name.gsub(/[A-Z]/) { |x| "_#{x.downcase}" }
202
+ # _exposed_ports -> exposed_ports
203
+ name = name[1..-1] if name.start_with?('_')
204
+ name
288
205
  end
289
206
 
290
207
  def from_image_from_action_handler(action_handler, machine_spec)
@@ -298,22 +215,11 @@ module DockerDriver
298
215
  end
299
216
  end
300
217
 
301
- def driver_url
302
- "docker:#{Docker.url}"
303
- end
304
-
305
- def start_machine(action_handler, machine_spec, machine_options)
306
- container = container_for(machine_spec)
307
- if container && !container.info['State']['Running']
308
- container.start
309
- end
310
- end
311
-
312
218
  def machine_for(machine_spec, machine_options)
313
219
  Chef::Log.debug('machine_for...')
314
- docker_options = machine_options[:docker_options] || Mash.from_hash(machine_spec.reference['docker_options'])
220
+ docker_options = machine_options[:docker_options] || Mash.from_hash(machine_spec.reference['docker_options'] || {})
315
221
 
316
- container = Docker::Container.get(machine_spec.reference['container_id'], @connection)
222
+ container = container_for(machine_spec)
317
223
 
318
224
  if machine_spec.from_image
319
225
  convergence_strategy = Chef::Provisioning::ConvergenceStrategy::NoConverge.new({}, config)
@@ -328,28 +234,31 @@ module DockerDriver
328
234
  machine_spec,
329
235
  transport,
330
236
  convergence_strategy,
237
+ @connection,
331
238
  docker_options[:command]
332
239
  )
333
240
  end
334
241
 
335
242
  def container_for(machine_spec)
336
- container_id = machine_spec.reference['container_id']
337
243
  begin
338
- container = Docker::Container.get(container_id, @connection) if container_id
244
+ Docker::Container.get(machine_spec.name, {}, @connection)
339
245
  rescue Docker::Error::NotFoundError
340
246
  end
341
247
  end
342
248
 
343
- def base_image_for(machine_spec)
344
- Chef::Log.debug("Looking for image #{machine_spec.from_image}")
345
- image_spec = machine_spec.managed_entry_store.get!(:machine_image, machine_spec.from_image)
346
- Mash.new(image_spec.reference)[:docker_options][:base_image]
249
+ def image_for(image_spec)
250
+ begin
251
+ Docker::Image.get(image_spec.name, {}, @connection)
252
+ rescue Docker::Error::NotFoundError
253
+ end
347
254
  end
348
255
 
349
256
  def stringize_keys(hash)
350
- hash.each_with_object({}) do |(k,v),hash|
351
- v = stringize_keys(v) if v.is_a?(Hash)
352
- hash[k.to_s] = v
257
+ if hash
258
+ hash.each_with_object({}) do |(k,v),hash|
259
+ v = stringize_keys(v) if v.is_a?(Hash)
260
+ hash[k.to_s] = v
261
+ end
353
262
  end
354
263
  end
355
264
  end
@@ -1,7 +1,7 @@
1
1
  class Chef
2
2
  module Provisioning
3
3
  module DockerDriver
4
- VERSION = '1.0.0.beta.1'
4
+ VERSION = '1.0.0.beta.2'
5
5
  end
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chef-provisioning-docker
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta.1
4
+ version: 1.0.0.beta.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Duffield
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-11 00:00:00.000000000 Z
11
+ date: 2016-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef
@@ -44,14 +44,20 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.25'
47
+ version: '1.26'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 1.26.2
48
51
  type: :runtime
49
52
  prerelease: false
50
53
  version_requirements: !ruby/object:Gem::Requirement
51
54
  requirements:
52
55
  - - "~>"
53
56
  - !ruby/object:Gem::Version
54
- version: '1.25'
57
+ version: '1.26'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.26.2
55
61
  - !ruby/object:Gem::Dependency
56
62
  name: minitar
57
63
  requirement: !ruby/object:Gem::Requirement
@@ -136,7 +142,9 @@ files:
136
142
  - Rakefile
137
143
  - chef-provisioning-docker.gemspec
138
144
  - lib/chef/provisioning/docker_driver.rb
145
+ - lib/chef/provisioning/docker_driver/chef_zero_http_proxy.rb
139
146
  - lib/chef/provisioning/docker_driver/docker_container_machine.rb
147
+ - lib/chef/provisioning/docker_driver/docker_run_options.rb
140
148
  - lib/chef/provisioning/docker_driver/docker_transport.rb
141
149
  - lib/chef/provisioning/docker_driver/driver.rb
142
150
  - lib/chef/provisioning/docker_driver/version.rb
@@ -168,4 +176,3 @@ signing_key:
168
176
  specification_version: 4
169
177
  summary: Provisioner for creating Docker containers in Chef Provisioning.
170
178
  test_files: []
171
- has_rdoc: