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.
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