chef-metal-docker 0.2 → 0.4.1

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: 9f6ac421bc65fa8a3497942dbd04d15efb23badc
4
- data.tar.gz: 1b23d70778b4bbece79836bb419cb3ecd462fc19
3
+ metadata.gz: 423d34630e6382c7d8d9ad91597e5d34e5b7191b
4
+ data.tar.gz: 702af2043c4db494e3b67ed8c38bf9c94de24d45
5
5
  SHA512:
6
- metadata.gz: ec3d7947e1e67187603842051249713a662ee9f800a8d4abd72fd10334a4e692c0005fd1f25c34a7121b01a5f6a5c3e5aea8f3f18416a2e8d5ccb882c943e3ce
7
- data.tar.gz: a2df491bf80e944a349f256e333d6e759f2b4e2f46aab7a3ed6e7727507338a9c93dda64331ae516f50761a35b29464e70637891bb43f3dd44f8d47bb7629a72
6
+ metadata.gz: 149d95a39c819994ecf0cbbd7d2784f4a4b224d174d96c7a1860095340e8e554793d1de341ab5acc5be496fe41324bc79022f1b6e6ac71c2ece5c228818668b2
7
+ data.tar.gz: 7f4a173fde92f6a011aac0d221c2499ffeb382109d330b5e205236358a6222a1ba329eabda08d91b14a64acf6e15ddfcef9d0553ca04770624cb331b2cbdc799
data/README.md CHANGED
@@ -1,3 +1,64 @@
1
1
  # chef-metal-docker
2
2
 
3
- This is the Docker provisioner for chef-metal.
3
+ How to use:
4
+
5
+ First you need to ensure that Docker is running. This can be done on a Linux host using Docker's installers or on OSX using boot2docker. Once you have that, you can install the dependencies with Bundler and then use the Docker driver like the following:
6
+
7
+ ```
8
+ CHEF_DRIVER=docker bundle exec chef-client -z docker_ubuntu_image.rb
9
+ ```
10
+
11
+ This will run Chef-zero and use the description stored in docker_ubuntu_image.rb (the second example below). Note that some configuration syntax is likely to change a little bit so be sure to check the documentation.
12
+
13
+ ## Machine creation
14
+
15
+ Using this driver, you can then define a machine similar to the following example:
16
+
17
+ ```ruby
18
+ require 'chef_metal_docker'
19
+
20
+ machine 'wario' do
21
+ recipe 'apache'
22
+
23
+ machine_options :docker_options => {
24
+ :base_image => {
25
+ :name => 'ubuntu',
26
+ :repository => 'ubuntu',
27
+ :tag => '14.04'
28
+ },
29
+ :command => '/usr/sbin/httpd'
30
+ }
31
+ end
32
+ ```
33
+
34
+ This will create a docker container based on Ubuntu 14.04 and
35
+ then execute the Apache recipe and run the /usr/sbin/httpd command
36
+ as the container's run command.
37
+
38
+ ## Machine images
39
+
40
+ This driver supports the new machine image paradigm; with Docker you can build a base image, save that and use it to create a new container. Here is an example of this:
41
+
42
+ ```ruby
43
+ require 'chef_metal_docker'
44
+
45
+ machine_image 'web_server' do
46
+ recipe 'apache'
47
+
48
+ machine_options :docker_options => {
49
+ :base_image => {
50
+ :name => 'ubuntu',
51
+ :repository => 'ubuntu',
52
+ :tag => '14.04'
53
+ }
54
+ }
55
+ end
56
+
57
+ machine 'web00' do
58
+ from_image 'web_server'
59
+
60
+ machine_options :docker_options => {
61
+ :command => '/usr/sbin/httpd'
62
+ }
63
+ end
64
+ ```
@@ -1,127 +1,21 @@
1
1
  require 'chef/provider/lwrp_base'
2
- require 'chef_metal_docker/helpers/container'
3
2
 
4
3
  class Chef::Provider::DockerContainer < Chef::Provider::LWRPBase
5
4
 
6
- include ChefMetalDocker::Helpers::Container
7
-
8
5
  def whyrun_supported?
9
6
  true
10
7
  end
11
8
 
12
9
  def load_current_resource
13
- @current_resource = Chef::Resource::DockerContainer.new(new_resource)
14
- wait_until_ready!
15
- docker_containers.each do |ps|
16
- unless container_id_matches?(ps['id'])
17
- next unless container_image_matches?(ps['image'])
18
- next unless container_command_matches_if_exists?(ps['command'])
19
- next unless container_name_matches_if_exists?(ps['names'])
20
- end
21
- Chef::Log.debug('Matched docker container: ' + ps['line'].squeeze(' '))
22
- @current_resource.container_name(ps['names'])
23
- @current_resource.created(ps['created'])
24
- @current_resource.id(ps['id'])
25
- @current_resource.status(ps['status'])
26
- break
27
- end
28
- @current_resource
29
- end
30
-
31
- action :commit do
32
- if exists?
33
- converge_by("create docker image based on #{current_resource.id}") do
34
- commit
35
- end
36
- end
37
- end
38
-
39
- action :cp do
40
- if exists?
41
- converge_by("copy #{new_resource.source} from #{current_resource.id} to #{new_resource.desitnation}") do
42
- cp
43
- end
44
- end
45
- end
46
-
47
- action :export do
48
- if exists?
49
- converge_by("export the contents of #{current_resource.id} to #{new_resource.desitniation}") do
50
- export
51
- end
52
- end
53
- end
54
-
55
- action :kill do
56
- if running?
57
- converge_by("kill container #{current_resource.id}") do
58
- kill
59
- end
60
- end
61
- end
62
-
63
- action :redeploy do
64
- converge_by("redeploy container #{current_resource.id}") do
65
- redeploy
66
- end
67
- end
68
-
69
- action :remove do
70
- if running?
71
- converge_by("stop container #{current_resource.id}") do
72
- stop
73
- end
74
- end
75
- if exists?
76
- converge_by("remove container #{current_resource.id}") do
77
- remove
78
- end
79
- end
10
+ # Don't do this here...
80
11
  end
81
12
 
82
- action :restart do
83
- if exists?
84
- converge_by("restart container #{current_resource.id}") do
85
- restart
86
- end
87
- end
88
- end
13
+ action :create do
89
14
 
90
- action :run do
91
- unless running?
92
- if exists?
93
- converge_by("container #{current_resource.id} already exists...starting") do
94
- start
95
- end
96
- else
97
- converge_by("run container #{current_resource.id}") do
98
- run
99
- end
100
- end
101
- end
102
15
  end
103
16
 
104
- action :start do
105
- unless running?
106
- converge_by("start container #{current_resource.id}") do
107
- start
108
- end
109
- end
110
- end
17
+ action :delete do
111
18
 
112
- action :stop do
113
- if running?
114
- converge_by("stop container #{current_resource.id}") do
115
- stop
116
- end
117
- end
118
19
  end
119
20
 
120
- action :wait do
121
- if running?
122
- converge_by("tell container #{current_resource.id} to wait") do
123
- wait
124
- end
125
- end
126
- end
127
21
  end
@@ -1,12 +1,6 @@
1
1
  require 'chef/resource/lwrp_base'
2
2
 
3
- class Chef::Resource::DockerContainer < Chef::Resource::LWRPBase
4
-
5
- self.resource_name = 'docker_container'
6
-
7
- actions :commit, :cp, :export, :kill, :redeploy, :remove, :restart, :run, :start, :stop, :wait
8
-
9
- default_action :run
3
+ class Chef::Resource::DockerContainer < Chef::Resource::Machine
10
4
 
11
5
  attribute :image, :name_attribute => true
12
6
  attribute :attach, :kind_of => [TrueClass, FalseClass]
@@ -0,0 +1,3 @@
1
+ require 'chef_metal_docker/docker_driver'
2
+
3
+ ChefMetal.register_driver_class('docker', ChefMetalDocker::DockerDriver)
@@ -1,4 +1,4 @@
1
1
  require 'chef_metal'
2
- require 'chef_metal_docker/docker_provisioner'
2
+ require 'chef_metal_docker/docker_driver'
3
3
  require 'chef/resource/docker_container'
4
- require 'chef/provider/docker_container'
4
+ require 'chef/provider/docker_container'
@@ -0,0 +1,92 @@
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
+ request_line = to_client.readline
33
+
34
+ verb = request_line[/^\w+/]
35
+ url = request_line[/^\w+\s+(\S+)/, 1]
36
+ version = request_line[/HTTP\/(1\.\d)\s*$/, 1]
37
+ uri = URI::parse url
38
+
39
+ # Show what got requested
40
+ Chef::Log.debug("[C->S]: #{verb} -> #{url}")
41
+
42
+ querystr = if uri.query
43
+ "#{uri.path}?#{uri.query}"
44
+ else
45
+ uri.path
46
+ end
47
+
48
+ to_server = TCPSocket.new(@remote_address, @remote_port)
49
+
50
+ to_server.write("#{verb} #{querystr} HTTP/#{version}\r\n")
51
+
52
+ content_len = 0
53
+
54
+ loop do
55
+ line = to_client.readline
56
+
57
+ if line =~ /^Content-Length:\s+(\d+)\s*$/
58
+ content_len = $1.to_i
59
+ end
60
+
61
+ # Strip proxy headers
62
+ if line =~ /^proxy/i
63
+ next
64
+ elsif line.strip.empty?
65
+ to_server.write("Connection: close\r\n\r\n")
66
+
67
+ if content_len >= 0
68
+ to_server.write(to_client.read(content_len))
69
+ Chef::Log.debug("[C->S]: Wrote #{content_len} bytes")
70
+ end
71
+
72
+ break
73
+ else
74
+ to_server.write(line)
75
+ end
76
+ end
77
+
78
+ buff = ''
79
+ loop do
80
+ to_server.read(8192, buff)
81
+ to_client.write(buff)
82
+ break if buff.size < 8192
83
+ end
84
+
85
+ # Close the sockets
86
+ to_client.close
87
+ to_server.close
88
+ end
89
+
90
+ end
91
+
92
+
@@ -0,0 +1,26 @@
1
+ require 'chef_metal/machine/unix_machine'
2
+
3
+ module ChefMetalDocker
4
+ class DockerContainerMachine < ChefMetal::Machine::UnixMachine
5
+
6
+ def initialize(machine_spec, transport, convergence_strategy, command)
7
+ super(machine_spec, transport, convergence_strategy)
8
+ @command = command
9
+ @container_name = machine_spec.location['container_name']
10
+ @transport = transport
11
+ end
12
+
13
+ def execute_always(command, options = {})
14
+ transport.execute(command, { :read_only => true }.merge(options))
15
+ end
16
+
17
+ def converge(action_handler)
18
+ super action_handler
19
+ if @command
20
+ Chef::Log.debug("DockerContainerMachine converge complete, executing #{@command} in #{@container_name}")
21
+ @transport.execute(@command)
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,238 @@
1
+
2
+ require 'chef/mixin/shell_out'
3
+ require 'chef_metal/driver'
4
+ require 'chef_metal_docker/version'
5
+ require 'chef_metal_docker/docker_transport'
6
+ require 'chef_metal_docker/docker_container_machine'
7
+ require 'chef_metal/convergence_strategy/install_cached'
8
+ require 'chef_metal/convergence_strategy/no_converge'
9
+
10
+ require 'yaml'
11
+ require 'docker/container'
12
+ require 'docker'
13
+
14
+ module ChefMetalDocker
15
+ # Provisions machines using Docker
16
+ class DockerDriver < ChefMetal::Driver
17
+
18
+ include Chef::Mixin::ShellOut
19
+
20
+ attr_reader :connection
21
+
22
+ # URL scheme:
23
+ # docker:<path>
24
+ # canonical URL calls realpath on <path>
25
+ def self.from_url(driver_url, config)
26
+ DockerDriver.new(driver_url, config)
27
+ end
28
+
29
+ def initialize(driver_url, config)
30
+ super
31
+ url = DockerDriver.connection_url(driver_url)
32
+
33
+ if url
34
+ # Export this as it's expected
35
+ # to be set for command-line utilities
36
+ ENV['DOCKER_HOST'] = url
37
+ Chef::Log.debug("Setting Docker URL to #{url}")
38
+ Docker.url = url
39
+ end
40
+
41
+ @connection = Docker.connection
42
+ end
43
+
44
+ def self.canonicalize_url(driver_url, config)
45
+ url = DockerDriver.connection_url(driver_url)
46
+ [ "docker:#{url}", config ]
47
+ end
48
+
49
+ # Parse the url from a driver URL, try to clean it up
50
+ # Returns a proper URL from the driver_url string. Examples include:
51
+ # docker:/var/run/docker.sock => unix:///var/run/docker.sock
52
+ # docker:192.168.0.1:1234 => tcp://192.168.0.1:1234
53
+ def self.connection_url(driver_url)
54
+ scheme, url = driver_url.split(':', 2)
55
+
56
+ if url && url.size > 0
57
+ # Clean up the URL with the protocol if needed (within reason)
58
+ case url
59
+ when /^\d+\.\d+\.\d+\.\d+:\d+$/
60
+ "tcp://#{url}"
61
+ when /^\//
62
+ "unix://#{url}"
63
+ when /^(tcp|unix)/
64
+ url
65
+ else
66
+ "tcp://#{url}"
67
+ end
68
+ end
69
+ end
70
+
71
+
72
+ def allocate_machine(action_handler, machine_spec, machine_options)
73
+
74
+ container_name = machine_spec.name
75
+ machine_spec.location = {
76
+ 'driver_url' => driver_url,
77
+ 'driver_version' => ChefMetalDocker::VERSION,
78
+ 'allocated_at' => Time.now.utc.to_s,
79
+ 'host_node' => action_handler.host_node,
80
+ 'container_name' => container_name,
81
+ 'image_id' => machine_options[:image_id]
82
+ }
83
+ end
84
+
85
+ def ready_machine(action_handler, machine_spec, machine_options)
86
+ base_image_name = build_container(machine_spec, machine_options)
87
+ start_machine(action_handler, machine_spec, machine_options, base_image_name)
88
+ machine_for(machine_spec, machine_options, base_image_name)
89
+ end
90
+
91
+ def build_container(machine_spec, machine_options)
92
+
93
+ docker_options = machine_options[:docker_options]
94
+
95
+ base_image = docker_options[:base_image]
96
+ source_name = base_image[:name]
97
+ source_repository = base_image[:repository]
98
+ source_tag = base_image[:tag]
99
+
100
+ # Don't do this if we're loading from an image
101
+ if docker_options[:from_image]
102
+ "#{source_repository}:#{source_tag}"
103
+ else
104
+ target_repository = 'chef'
105
+ target_tag = machine_spec.name
106
+
107
+ image = find_image(target_repository, target_tag)
108
+
109
+ # kick off image creation
110
+ if image == nil
111
+ Chef::Log.debug("No matching images for #{target_repository}:#{target_tag}, creating!")
112
+ image = Docker::Image.create('fromImage' => source_name,
113
+ 'repo' => source_repository ,
114
+ 'tag' => source_tag)
115
+ Chef::Log.debug("Allocated #{image}")
116
+ image.tag('repo' => 'chef', 'tag' => target_tag)
117
+ Chef::Log.debug("Tagged image #{image}")
118
+ end
119
+
120
+ "#{target_repository}:#{target_tag}"
121
+ end
122
+ end
123
+
124
+ def allocate_image(action_handler, image_spec, image_options, machine_spec)
125
+ # Set machine options on the image to match our newly created image
126
+ image_spec.machine_options = {
127
+ :docker_options => {
128
+ :base_image => {
129
+ :name => "chef_#{image_spec.name}",
130
+ :repository => 'chef',
131
+ :tag => image_spec.name
132
+ },
133
+ :from_image => true
134
+ }
135
+ }
136
+ end
137
+
138
+ def ready_image(action_handler, image_spec, image_options)
139
+ Chef::Log.debug('READY IMAGE!')
140
+ end
141
+
142
+ # Connect to machine without acquiring it
143
+ def connect_to_machine(machine_spec, machine_options)
144
+ Chef::Log.debug('Connect to machine!')
145
+ end
146
+
147
+ def destroy_machine(action_handler, machine_spec, machine_options)
148
+ container_name = machine_spec.location['container_name']
149
+ Chef::Log.debug("Destroying container: #{container_name}")
150
+ container = Docker::Container.get(container_name, @connection)
151
+
152
+ begin
153
+ Chef::Log.debug("Stopping #{container_name}")
154
+ container.stop
155
+ rescue Excon::Errors::NotModified
156
+ # this is okay
157
+ Chef::Log.debug('Already stopped!')
158
+ end
159
+
160
+ Chef::Log.debug("Removing #{container_name}")
161
+ container.delete
162
+
163
+ Chef::Log.debug("Destroying image: chef:#{container_name}")
164
+ image = Docker::Image.get("chef:#{container_name}")
165
+ image.delete
166
+
167
+ end
168
+
169
+ def stop_machine(action_handler, node)
170
+ Chef::Log.debug("Stop machine: #{node.inspect}")
171
+ end
172
+
173
+ def image_named(image_name)
174
+ Docker::Image.all.select {
175
+ |i| i.info['RepoTags'].include? image_name
176
+ }.first
177
+ end
178
+
179
+ def find_image(repository, tag)
180
+ Docker::Image.all.select {
181
+ |i| i.info['RepoTags'].include? "#{repository}:#{tag}"
182
+ }.first
183
+ end
184
+
185
+ def driver_url
186
+ "docker:#{Docker.url}"
187
+ end
188
+
189
+ def start_machine(action_handler, machine_spec, machine_options, base_image_name)
190
+ # Spin up a docker instance if needed, otherwise use the existing one
191
+ container_name = machine_spec.location['container_name']
192
+
193
+ begin
194
+ Docker::Container.get(container_name, @connection)
195
+ rescue Docker::Error::NotFoundError
196
+ docker_options = machine_options[:docker_options]
197
+ Chef::Log.debug("Start machine for container #{container_name} using base image #{base_image_name} with options #{docker_options.inspect}")
198
+ image = image_named(base_image_name)
199
+ container = Docker::Container.create('Image' => image.id, 'name' => container_name)
200
+ Chef::Log.debug("Container id: #{container.id}")
201
+ machine_spec.location['container_id'] = container.id
202
+ end
203
+
204
+ end
205
+
206
+ def machine_for(machine_spec, machine_options, base_image_name)
207
+ Chef::Log.debug('machine_for...')
208
+
209
+ docker_options = machine_options[:docker_options]
210
+
211
+ transport = DockerTransport.new(machine_spec.location['container_name'],
212
+ base_image_name,
213
+ nil,
214
+ Docker.connection)
215
+
216
+ convergence_strategy = if docker_options[:from_image]
217
+ ChefMetal::ConvergenceStrategy::NoConverge.new({}, config)
218
+ else
219
+ convergence_strategy_for(machine_spec, machine_options)
220
+ end
221
+
222
+ ChefMetalDocker::DockerContainerMachine.new(
223
+ machine_spec,
224
+ transport,
225
+ convergence_strategy,
226
+ docker_options[:command]
227
+ )
228
+ end
229
+
230
+ def convergence_strategy_for(machine_spec, machine_options)
231
+ @unix_convergence_strategy ||= begin
232
+ ChefMetal::ConvergenceStrategy::InstallCached.
233
+ new(machine_options[:convergence_options], config)
234
+ end
235
+ end
236
+
237
+ end
238
+ end