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

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a24a00feb49b30d7a76c5f0d2c6441114b961264
4
- data.tar.gz: 4c24b6bd38a87d0afd01127e9266610e5efe1624
3
+ metadata.gz: 08110106616725e446a740a4a3a309ab9afca476
4
+ data.tar.gz: d35e453ec12ac3b2a2fcb33999fcb4d3014d3fc3
5
5
  SHA512:
6
- metadata.gz: c86add037f7fcb734e8884ab2116d2b13cc5eb9cbb2cd0c976e58f7e37313bde3ba82979aa950c6313c84d3ca5b048201d0b1ab7a067a378508d897773e7eeb2
7
- data.tar.gz: ea5067b1773a9ec1eb218989de89d5023723a5fdfb3bdca40aa07d417f31e36f3ebe283847ca26d39f790c96eaac3b32b4b250633fe6bfe74f39562bb1cf172b
6
+ metadata.gz: 664c9d9ff139e4f84425b2f5561053c4bf7da9fc610d280830575dcf768401a1febfc84a2c7fb4d3d3aebc1adbd3ca7d66a530a4b44488757ea49e009f13bb05
7
+ data.tar.gz: 81605d7138ce4f1b95a7725ec4748af023811c768bfd181676a94b9fd680b258ad01879292f0897a6fbce56d4a1f4e077f1d43d2ef0ee0318f0cd040fcf1132e
data/README.md CHANGED
@@ -16,15 +16,16 @@ Using this , you can then define a machine similar to the following example:
16
16
 
17
17
  ```ruby
18
18
  require 'chef/provisioning/docker_driver'
19
+ with_driver 'docker'
19
20
 
20
21
  machine 'wario' do
21
- recipe 'openssh::default'
22
+ recipe 'openssh::default'
22
23
 
23
- machine_options :docker_options => {
24
- :base_image => {
25
- :name => 'ubuntu',
26
- :repository => 'ubuntu',
27
- :tag => '14.04'
24
+ machine_options docker_options: {
25
+ base_image: {
26
+ name: 'ubuntu',
27
+ repository: 'ubuntu',
28
+ tag: '14.04'
28
29
  },
29
30
  :command => '/usr/sbin/sshd -p 8022 -D',
30
31
 
@@ -97,4 +98,3 @@ end
97
98
  This will create a docker container based on Ubuntu 14.04 and
98
99
  then execute the Apache recipe and run the /usr/sbin/httpd command
99
100
  as the container's run command.
100
-
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.add_dependency 'chef'
16
16
  s.add_dependency 'chef-provisioning', '~> 1.0'
17
- s.add_dependency 'docker-api', '~> 1.25'
17
+ s.add_dependency 'docker-api', '~> 1.26', '>= 1.26.2'
18
18
  s.add_dependency 'minitar'
19
19
  s.add_dependency 'sys-proctable'
20
20
 
@@ -0,0 +1,95 @@
1
+ require 'socket'
2
+ require 'uri'
3
+
4
+ class ChefZeroHttpProxy
5
+
6
+ def initialize(local_address, local_port, remote_address, remote_port)
7
+ @local_address = local_address
8
+ @local_port = local_port
9
+ @remote_address = remote_address
10
+ @remote_port = remote_port
11
+ end
12
+
13
+ def run
14
+ begin
15
+ Chef::Log.debug("Running proxy main loop on #{@local_address}:#{@local_port}!")
16
+
17
+ # Start our server to handle connections (will raise things on errors)
18
+ @socket = TCPServer.new @local_address, @local_port
19
+
20
+ # Handle every request in another thread
21
+ loop do
22
+ s = @socket.accept
23
+ Thread.new s, &method(:handle_request)
24
+ end
25
+
26
+ ensure
27
+ @socket.close if @socket
28
+ end
29
+ end
30
+
31
+ def handle_request(to_client)
32
+ begin
33
+ request_line = to_client.readline
34
+
35
+ verb = request_line[/^\w+/]
36
+ url = request_line[/^\w+\s+(\S+)/, 1]
37
+ version = request_line[/HTTP\/(1\.\d)\s*$/, 1]
38
+ uri = URI::parse url
39
+
40
+ # Show what got requested
41
+ Chef::Log.debug("[C->S]: #{verb} -> #{url}")
42
+
43
+ querystr = if uri.query
44
+ "#{uri.path}?#{uri.query}"
45
+ else
46
+ uri.path
47
+ end
48
+
49
+ to_server = TCPSocket.new(@remote_address, @remote_port)
50
+
51
+ to_server.write("#{verb} #{querystr} HTTP/#{version}\r\n")
52
+
53
+ content_len = 0
54
+
55
+ loop do
56
+ line = to_client.readline
57
+
58
+ if line =~ /^Content-Length:\s+(\d+)\s*$/
59
+ content_len = $1.to_i
60
+ end
61
+
62
+ # Strip proxy headers
63
+ if line =~ /^proxy/i
64
+ next
65
+ elsif line.strip.empty?
66
+ to_server.write("Connection: close\r\n\r\n")
67
+
68
+ if content_len >= 0
69
+ to_server.write(to_client.read(content_len))
70
+ Chef::Log.debug("[C->S]: Wrote #{content_len} bytes")
71
+ end
72
+
73
+ break
74
+ else
75
+ to_server.write(line)
76
+ end
77
+ end
78
+
79
+ buff = ''
80
+ while to_server.read(8192, buff)
81
+ to_client.write(buff)
82
+ end
83
+
84
+ rescue
85
+ Chef::Log.error $!
86
+ raise
87
+
88
+ ensure
89
+ # Close the sockets
90
+ to_client.close
91
+ to_server.close
92
+ end
93
+ end
94
+
95
+ end
@@ -1,4 +1,5 @@
1
1
  require 'chef/provisioning/machine/unix_machine'
2
+ require 'chef/provisioning/docker_driver/docker_run_options'
2
3
 
3
4
  class Chef
4
5
  module Provisioning
@@ -9,29 +10,214 @@ module DockerDriver
9
10
  # Options is expected to contain the optional keys
10
11
  # :command => the final command to execute
11
12
  # :ports => a list of port numbers to listen on
12
- def initialize(machine_spec, transport, convergence_strategy, command = nil)
13
+ def initialize(machine_spec, transport, convergence_strategy, connection, command = nil)
13
14
  super(machine_spec, transport, convergence_strategy)
14
15
  @command = command
15
16
  @transport = transport
17
+ @connection = connection
18
+ end
19
+
20
+ def setup_convergence(action_handler)
21
+ # Build a converge container to converge in
22
+ transport.container = build_converge_container(action_handler)
23
+ unless transport.container.info['State']['Running']
24
+ action_handler.perform_action "start converge container chef-converge.#{machine_spec.name}" do
25
+ transport.container.start!
26
+ end
27
+ end
28
+ super(action_handler)
29
+ # Commit after convergence setup (such as the install of Chef)
30
+ # to break up the cost of the commit and avoid read timeouts
31
+ transport.container.commit
16
32
  end
17
33
 
18
34
  def converge(action_handler)
19
- super action_handler
20
- Chef::Log.debug("DockerContainerMachine converge complete, executing #{@command} in #{@container_name}")
21
- image = transport.container.commit(
22
- 'repo' => 'chef',
23
- 'tag' => machine_spec.reference['container_name']
35
+ # First, grab and start the converge container if it's there ...
36
+ transport.container = converge_container_for(machine_spec)
37
+ if !transport.container
38
+ raise "No converge container found! Did you run `:converge` without first running `:setup`?"
39
+ end
40
+ unless transport.container.info['State']['Running']
41
+ action_handler.perform_action "start converge container chef-converge.#{machine_spec.name}" do
42
+ transport.container.start!
43
+ end
44
+ end
45
+
46
+ # Then, converge ...
47
+ super(action_handler)
48
+
49
+ # Save the converged image ...
50
+ converged_image = commit_converged_image(action_handler, machine_spec, transport.container)
51
+
52
+ # Build the new container
53
+ transport.container = create_container(action_handler, machine_spec, converged_image)
54
+
55
+ # Finally, start it!
56
+ action_handler.perform_action "start container #{machine_spec.name}" do
57
+ transport.container.start!
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def container_config(action_handler, machine_spec)
64
+ docker_options = machine_spec.reference['docker_options'] || {}
65
+
66
+ # We're going to delete things to make it easier on ourselves, back it up
67
+ docker_options = docker_options.dup
68
+
69
+ # Bring in from_image
70
+ if machine_spec.from_image
71
+ docker_options['base_image'] ||= {}
72
+ docker_options['base_image']['name'] = machine_spec.from_image
73
+ end
74
+
75
+ # Respect :container_config
76
+ config = stringize_keys(docker_options.delete('container_config') || {})
77
+
78
+ # Respect :base_image
79
+ image = base_image(action_handler, docker_options.delete('base_image'))
80
+ config['Image'] = image if image
81
+
82
+ # Respect everything else
83
+ DockerRunOptions.include_command_line_options_in_container_config(config, docker_options)
84
+ end
85
+
86
+ # Get the converge container for this machine
87
+ def converge_container_for(machine_spec)
88
+ begin
89
+ Docker::Container.get("chef-converge.#{machine_spec.name}", {}, @connection)
90
+ rescue Docker::Error::NotFoundError
91
+ end
92
+ end
93
+
94
+ def container_for(machine_spec)
95
+ begin
96
+ Docker::Container.get(machine_spec.name, {}, @connection)
97
+ rescue Docker::Error::NotFoundError
98
+ end
99
+ end
100
+
101
+ # Builds a container that has the same properties as the final container,
102
+ # but with a couple of tweaks to allow processes to run and converge the
103
+ # container.
104
+ def build_converge_container(action_handler)
105
+ # If a converge container already exists, do nothing. TODO check if it's different!!!
106
+ converge_container = converge_container_for(machine_spec)
107
+ if converge_container
108
+ return converge_container
109
+ end
110
+
111
+ # Create a chef-capable container (just like the final one, but with --net=host
112
+ # and a command that keeps it open). Base it on the image.
113
+ config = container_config(action_handler, machine_spec)
114
+ config.merge!(
115
+ 'name' => "chef-converge.#{machine_spec.reference['container_name']}",
116
+ 'Cmd' => [ "/bin/sh", "-c", "while true;do sleep 1000; done" ],
24
117
  )
25
- machine_spec.reference['image_id'] = image.id
118
+ # If we're using Docker Toolkit, we need to use host networking for the converge
119
+ # so we can open up the port we need. Don't force it in other cases, though.
120
+ if transport.is_local_machine(URI(transport.config[:chef_server_url]).host) &&
121
+ transport.docker_toolkit_transport(@connection.url)
122
+ config['HostConfig'] ||= {}
123
+ config['HostConfig'].merge!('NetworkMode' => 'host')
124
+ # These are incompatible with NetworkMode: host
125
+ config['HostConfig'].delete('Links')
126
+ config['HostConfig'].delete('ExtraHosts')
127
+ config.delete('NetworkSettings')
128
+ end
129
+ # Don't use any resources that need to be shared (such as exposed ports)
130
+ config.delete('ExposedPorts')
131
+
132
+ Chef::Log.debug("Creating converge container with config #{config} ...")
133
+ action_handler.perform_action "create container to converge #{machine_spec.name}" do
134
+ # create deletes the name :(
135
+ Docker::Container.create(config.dup, @connection)
136
+ converge_container = Docker::Container.get(config['name'], {}, @connection)
137
+ Chef::Log.debug("Created converge container #{converge_container.id}")
138
+ end
139
+ converge_container
140
+ end
141
+
142
+ # Commit the converged container to an image. Called by converge.
143
+ def commit_converged_image(action_handler, machine_spec, converge_container)
144
+ # Commit the converged container to an image
145
+ converged_image = nil
146
+ action_handler.perform_action "commit and delete converged container for #{machine_spec.name}" do
147
+ converged_image = converge_container.commit
148
+ converge_container.stop!
149
+ converge_container.delete
150
+ end
151
+ converged_image
152
+ end
153
+
154
+ # Create the final container from the converged image
155
+ def create_container(action_handler, machine_spec, converged_image)
156
+ # Check if the container already exists.
157
+ container = container_for(machine_spec)
158
+ if container
159
+ # If it's the same image, just return; don't stop and start.
160
+ if container.info['Image'] == converged_image.id
161
+ return container
162
+ else
163
+ # If the container exists but is based on an old image, destroy it.
164
+ action_handler.perform_action "stop and delete running container for #{machine_spec.name}" do
165
+ container.stop!
166
+ container.delete
167
+ end
168
+ end
169
+ end
26
170
 
27
- if @command && transport.container.info['Config']['Cmd'].join(' ') != @command
28
- transport.container.delete(:force => true)
29
- container = image.run(Shellwords.split(@command))
30
- container.rename(machine_spec.reference['container_name'])
171
+ # Create the new container
172
+ config = container_config(action_handler, machine_spec)
173
+ config.merge!(
174
+ 'name' => machine_spec.reference['container_name'],
175
+ 'Image' => converged_image.id
176
+ )
177
+ action_handler.perform_action "create final container for #{machine_spec.name}" do
178
+ container = Docker::Container.create(config, @connection)
31
179
  machine_spec.reference['container_id'] = container.id
32
- transport.container = container
180
+ machine_spec.save(action_handler)
181
+ end
182
+ container
183
+ end
184
+
185
+ def stringize_keys(hash)
186
+ hash.each_with_object({}) do |(k,v),hash|
187
+ v = stringize_keys(v) if v.is_a?(Hash)
188
+ hash[k.to_s] = v
189
+ end
190
+ end
191
+
192
+ def base_image(action_handler, base_image_value)
193
+ case base_image_value
194
+ when Hash
195
+ params = base_image_value.dup
196
+ if !params['fromImage']
197
+ params['fromImage'] = params.delete('name')
198
+ params['fromImage'] = "#{params['fromImage']}:#{params.delete('tag')}" if params['tag']
199
+ end
200
+ when String
201
+ params = { 'fromImage' => base_image_value }
202
+ when nil
203
+ return nil
204
+ else
205
+ raise "Unexpected type #{base_image_value.class} for docker_options[:base_image]!"
206
+ end
207
+
208
+ image_name = params['fromImage']
209
+ repo, image_name = params['fromImage'].split('/', 2) if params['fromImage'].include?('/')
210
+
211
+ begin
212
+ image = Docker::Image.get(image_name, {}, @connection)
213
+ rescue Docker::Error::NotFoundError
214
+ # If it's not found, pull it.
215
+ action_handler.perform_action "pull #{params}" do
216
+ image = Docker::Image.create(params, @connection)
217
+ end
33
218
  end
34
- machine_spec.save(action_handler)
219
+
220
+ image.id
35
221
  end
36
222
  end
37
223
  end