chef-metal-docker 0.2 → 0.4.1

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