chef-provisioning-docker 0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 64bbb414a5fb8938463923ee377c803551cca0af
4
+ data.tar.gz: cbd0abd0ee49daceabce24aae225bfe4169bcab4
5
+ SHA512:
6
+ metadata.gz: a51b6596e0b15f8a274da03d1cde0ce2781d27461c9daccdcfb4d5f0b4c43ea5c1c5f2c8d4f2a52c005197d266327b2841f52b3c78746c44e8ea6d66edddfa9b
7
+ data.tar.gz: e520307871c6db26ea5502292f485a3bd55548c246cbf5c591ef89bbff8e990a145b847cc6c7e4e9da7d58256e7b691e5f517feaf5863f6fe6ddbe7463bed0b0
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # chef-provisioning-docker
2
+
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 like the following:
6
+
7
+ ```
8
+ CHEF_=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 , you can then define a machine similar to the following example:
16
+
17
+ ```ruby
18
+ require 'chef/provisioning/docker_driver'
19
+
20
+ machine 'wario' do
21
+ recipe 'openssh::default'
22
+
23
+ machine_options :docker_options => {
24
+ :base_image => {
25
+ :name => 'ubuntu',
26
+ :repository => 'ubuntu',
27
+ :tag => '14.04'
28
+ },
29
+ :command => '/usr/sbin/sshd -p 8022 -D',
30
+
31
+ # Ports can be one of two forms:
32
+ # src_port (string or integer) is a pass-through, i.e 8022 or "9933"
33
+ # src:dst (string) is a map from src to dst, i.e "8022:8023" maps 8022 externally to 8023 in the container
34
+
35
+ # Example (multiple):
36
+ :ports => [8022, "8023:9000", "9500"]
37
+
38
+ # Examples (single):
39
+ :ports => 1234
40
+ :ports => "2345:6789"
41
+
42
+ # if you need to keep stdin open (i.e docker run -i)
43
+ # :keep_stdin_open => true
44
+
45
+ }
46
+ end
47
+ ```
48
+
49
+ This will create a docker container based on Ubuntu 14.04 and
50
+ then execute the Apache recipe and run the /usr/sbin/httpd command
51
+ as the container's run command.
52
+
53
+ ## Machine images
54
+
55
+ This 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:
56
+
57
+ ```ruby
58
+ require 'chef/provisioning/docker_driver'
59
+
60
+ machine_image 'web_server' do
61
+ recipe 'apache'
62
+
63
+ machine_options :docker_options => {
64
+ :base_image => {
65
+ :name => 'ubuntu',
66
+ :repository => 'ubuntu',
67
+ :tag => '14.04'
68
+ }
69
+ }
70
+ end
71
+
72
+ machine 'web00' do
73
+ from_image 'web_server'
74
+
75
+ machine_options :docker_options => {
76
+ :command => '/usr/sbin/httpd'
77
+ }
78
+ end
79
+ ```
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+
4
+ task :spec do
5
+ require File.expand_path('spec/run')
6
+ end
@@ -0,0 +1,2 @@
1
+ require 'chef/provisioning'
2
+ require 'chef/provisioning/docker_driver/driver'
@@ -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,36 @@
1
+ require 'chef/provisioning/machine/unix_machine'
2
+
3
+ class Chef
4
+ module Provisioning
5
+ module DockerDriver
6
+ class DockerContainerMachine < Chef::Provisioning::Machine::UnixMachine
7
+
8
+ # Expects a machine specification, a usable transport and convergence strategy
9
+ # Options is expected to contain the optional keys
10
+ # :command => the final command to execute
11
+ # :ports => a list of port numbers to listen on
12
+ def initialize(machine_spec, transport, convergence_strategy, opts = {})
13
+ super(machine_spec, transport, convergence_strategy)
14
+ @command = opts[:command]
15
+ @ports = opts[:ports]
16
+ @keep_stdin_open = opts[:keep_stdin_open]
17
+ @container_name = machine_spec.location['container_name']
18
+ @transport = transport
19
+ end
20
+
21
+ def execute_always(command, options = {})
22
+ transport.execute(command, { :read_only => true }.merge(options))
23
+ end
24
+
25
+ def converge(action_handler)
26
+ super action_handler
27
+ if @command
28
+ Chef::Log.debug("DockerContainerMachine converge complete, executing #{@command} in #{@container_name}")
29
+ @transport.execute(@command, :detached => true, :read_only => true, :ports => @ports, :keep_stdin_open => @keep_stdin_open)
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,309 @@
1
+ require 'chef/provisioning/transport'
2
+ require 'docker'
3
+ require 'archive/tar/minitar'
4
+ require 'shellwords'
5
+ require 'uri'
6
+ require 'socket'
7
+ require 'mixlib/shellout'
8
+ require 'sys/proctable'
9
+ require 'chef/provisioning/docker_driver/chef_zero_http_proxy'
10
+
11
+ class Chef
12
+ module Provisioning
13
+ module DockerDriver
14
+ class DockerTransport < Chef::Provisioning::Transport
15
+ def initialize(container_name, base_image_name, credentials, connection, tunnel_transport = nil)
16
+ @repository_name = 'chef'
17
+ @container_name = container_name
18
+ @image = Docker::Image.get(base_image_name, connection)
19
+ @credentials = credentials
20
+ @connection = connection
21
+ @tunnel_transport = tunnel_transport
22
+ end
23
+
24
+ include Chef::Mixin::ShellOut
25
+
26
+ attr_reader :container_name
27
+ attr_reader :repository_name
28
+ attr_reader :image
29
+ attr_reader :credentials
30
+ attr_reader :connection
31
+ attr_reader :tunnel_transport
32
+
33
+ # Execute the specified command inside the container, returns a Mixlib::Shellout object
34
+ # Options contains the optional keys:
35
+ # :read_only => Do not commit this execute operation, just execute it
36
+ # :ports => ports to listen on (-p command-line options)
37
+ # :detached => true/false, execute this command in detached mode (for final program to run)
38
+ def execute(command, options={})
39
+ Chef::Log.debug("execute '#{command}' with options #{options}")
40
+
41
+ begin
42
+ connection.post("/containers/#{container_name}/stop?t=0", '')
43
+ Chef::Log.debug("stopped /containers/#{container_name}")
44
+ rescue Excon::Errors::NotModified
45
+ Chef::Log.debug("Already stopped #{container_name}")
46
+ rescue Docker::Error::NotFoundError
47
+ end
48
+
49
+ begin
50
+ # Delete the container if it exists and is dormant
51
+ connection.delete("/containers/#{container_name}?v=true&force=true")
52
+ Chef::Log.debug("deleted /containers/#{container_name}")
53
+ rescue Docker::Error::NotFoundError
54
+ end
55
+
56
+ command = Shellwords.split(command) if command.is_a?(String)
57
+
58
+ # TODO shell_out has no way to live stream stderr???
59
+ live_stream = nil
60
+ live_stream = STDOUT if options[:stream]
61
+ live_stream = options[:stream_stdout] if options[:stream_stdout]
62
+
63
+ args = ['docker', 'run', '--name', container_name]
64
+
65
+ if options[:detached]
66
+ args << '--detach'
67
+ end
68
+
69
+ if options[:ports]
70
+ options[:ports].each do |portnum|
71
+ args << '-p'
72
+ args << "#{portnum}"
73
+ end
74
+ end
75
+
76
+ if options[:keep_stdin_open]
77
+ args << '-i'
78
+ end
79
+
80
+ args << @image.id
81
+ args += command
82
+
83
+ cmdstr = Shellwords.join(args)
84
+ Chef::Log.debug("Executing #{cmdstr}")
85
+
86
+ # Remove this when https://github.com/opscode/chef/pull/2100 gets merged and released
87
+ # nullify live_stream because at the moment EventsOutputStream doesn't understand <<, which
88
+ # ShellOut uses
89
+ live_stream = nil unless live_stream.respond_to? :<<
90
+
91
+ cmd = Mixlib::ShellOut.new(cmdstr, :live_stream => live_stream, :timeout => execute_timeout(options))
92
+
93
+ cmd.run_command
94
+
95
+ unless options[:read_only]
96
+ Chef::Log.debug("Committing #{container_name} as #{repository_name}:#{container_name}")
97
+ container = Docker::Container.get(container_name)
98
+ @image = container.commit('repo' => repository_name, 'tag' => container_name)
99
+ end
100
+
101
+ Chef::Log.debug("Execute complete: status #{cmd.exitstatus}")
102
+
103
+ cmd
104
+ end
105
+
106
+ def read_file(path)
107
+ container = Docker::Container.create({
108
+ 'Image' => @image.id,
109
+ 'Cmd' => %w(echo true)
110
+ }, connection)
111
+ begin
112
+ tarfile = ''
113
+ # NOTE: this would be more efficient if we made it a stream and passed that to Minitar
114
+ container.copy(path) do |block|
115
+ tarfile << block
116
+ end
117
+ rescue Docker::Error::ServerError
118
+ if $!.message =~ /500/
119
+ return nil
120
+ else
121
+ raise
122
+ end
123
+ ensure
124
+ container.delete
125
+ end
126
+
127
+ output = ''
128
+ Archive::Tar::Minitar::Input.open(StringIO.new(tarfile)) do |inp|
129
+ inp.each do |entry|
130
+ while next_output = entry.read
131
+ output << next_output
132
+ end
133
+ entry.close
134
+ end
135
+ end
136
+
137
+ output
138
+ end
139
+
140
+ def write_file(path, content)
141
+ # TODO hate tempfiles. Find an in memory way.
142
+ Tempfile.open('metal_docker_write_file') do |file|
143
+ file.write(content)
144
+ file.close
145
+ @image = @image.insert_local('localPath' => file.path, 'outputPath' => path, 't' => "#{repository_name}:#{container_name}")
146
+ end
147
+ end
148
+
149
+ def download_file(path, local_path)
150
+ # TODO stream
151
+ file = File.open(local_path, 'w')
152
+ begin
153
+ file.write(read_file(path))
154
+ file.close
155
+ rescue
156
+ File.delete(file)
157
+ end
158
+ end
159
+
160
+ def upload_file(local_path, path)
161
+ @image = @image.insert_local('localPath' => local_path, 'outputPath' => path, 't' => "#{repository_name}:#{container_name}")
162
+ end
163
+
164
+ def make_url_available_to_remote(url)
165
+ # The host is already open to the container. Just find out its address and return it!
166
+ uri = URI(url)
167
+ host = Socket.getaddrinfo(uri.host, uri.scheme, nil, :STREAM)[0][3]
168
+ Chef::Log.debug("Making URL available: #{host}")
169
+
170
+ if host == '127.0.0.1' || host == '[::1]'
171
+ result = execute('ip route ls', :read_only => true)
172
+
173
+ Chef::Log.debug("IP route: #{result.stdout}")
174
+
175
+ if result.stdout =~ /default via (\S+)/
176
+
177
+ uri.host = if using_boot2docker?
178
+ # Intermediate VM does NAT, so local address should be fine here
179
+ Chef::Log.debug("Using boot2docker!")
180
+ IPSocket.getaddress(Socket.gethostname)
181
+ else
182
+ $1
183
+ end
184
+
185
+ if !@proxy_thread
186
+ # Listen to docker instances only, and forward to localhost
187
+ @proxy_thread = Thread.new do
188
+ Chef::Log.debug("Starting proxy thread: #{uri.host}:#{uri.port} <--> #{host}:#{uri.port}")
189
+ ChefZeroHttpProxy.new(uri.host, uri.port, host, uri.port).run
190
+ end
191
+ end
192
+ Chef::Log.debug("Using Chef server URL: #{uri.to_s}")
193
+
194
+ return uri.to_s
195
+ else
196
+ raise "Cannot forward port: ip route ls did not show default in expected format.\nSTDOUT: #{result.stdout}"
197
+ end
198
+ end
199
+ url
200
+ end
201
+
202
+ def disconnect
203
+ @proxy_thread.kill if @proxy_thread
204
+ end
205
+
206
+ def available?
207
+ end
208
+
209
+ private
210
+
211
+ # boot2docker introduces an intermediate VM so we need to use a slightly different
212
+ # mechanism for getting to the running chef-zero
213
+ def using_boot2docker?
214
+ Sys::ProcTable.ps do |proc|
215
+ if proc.respond_to?(:cmdline)
216
+ if proc.send(:cmdline).to_s =~ /.*--comment boot2docker.*/
217
+ return true
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ # Copy of container.attach with timeout support and pipeline
224
+ def attach_with_timeout(container, read_timeout, options = {}, &block)
225
+ opts = {
226
+ :stream => true, :stdout => true, :stderr => true
227
+ }.merge(options)
228
+ # Creates list to store stdout and stderr messages
229
+ msgs = Docker::Messages.new
230
+ connection.start_request(
231
+ :post,
232
+ "/containers/#{container.id}/attach",
233
+ opts,
234
+ :response_block => attach_for(block, msgs),
235
+ :read_timeout => read_timeout,
236
+ :pipeline => true,
237
+ :persistent => true
238
+ )
239
+ end
240
+
241
+ # Method that takes chunks and calls the attached block for each mux'd message
242
+ def attach_for(block, msg_stack)
243
+ messages = Docker::Messages.new
244
+ lambda do |c,r,t|
245
+ messages = messages.decipher_messages(c)
246
+ msg_stack.append(messages)
247
+
248
+ unless block.nil?
249
+ messages.stdout_messages.each do |msg|
250
+ block.call(:stdout, msg)
251
+ end
252
+ messages.stderr_messages.each do |msg|
253
+ block.call(:stderr, msg)
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ class DockerResult
260
+ def initialize(command, options, stdout, stderr, exitstatus)
261
+ @command = command
262
+ @options = options
263
+ @stdout = stdout
264
+ @stderr = stderr
265
+ @exitstatus = exitstatus
266
+ end
267
+
268
+ attr_reader :command
269
+ attr_reader :options
270
+ attr_reader :stdout
271
+ attr_reader :stderr
272
+ attr_reader :exitstatus
273
+
274
+ def error!
275
+ if exitstatus != 0
276
+ msg = "Error: command '#{command}' exited with code #{exitstatus}.\n"
277
+ msg << "STDOUT: #{stdout}" if !options[:stream] && !options[:stream_stdout] && Chef::Config.log_level != :debug
278
+ msg << "STDERR: #{stderr}" if !options[:stream] && !options[:stream_stderr] && Chef::Config.log_level != :debug
279
+ raise msg
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ class Docker::Connection
289
+ def start_request(method, *args, &block)
290
+ request = compile_request_params(method, *args, &block)
291
+ if Docker.logger
292
+ Docker.logger.debug(
293
+ [request[:method], request[:path], request[:query], request[:body]]
294
+ )
295
+ end
296
+ excon = resource
297
+ [ excon, excon.request(request) ]
298
+ rescue Excon::Errors::BadRequest => ex
299
+ raise ClientError, ex.message
300
+ rescue Excon::Errors::Unauthorized => ex
301
+ raise UnauthorizedError, ex.message
302
+ rescue Excon::Errors::NotFound => ex
303
+ raise NotFoundError, ex.message
304
+ rescue Excon::Errors::InternalServerError => ex
305
+ raise ServerError, ex.message
306
+ rescue Excon::Errors::Timeout => ex
307
+ raise TimeoutError, ex.message
308
+ end
309
+ end
@@ -0,0 +1,243 @@
1
+ require 'chef/mixin/shell_out'
2
+ require 'chef/provisioning/driver'
3
+ require 'chef/provisioning/docker_driver/version'
4
+ require 'chef/provisioning/docker_driver/docker_transport'
5
+ require 'chef/provisioning/docker_driver/docker_container_machine'
6
+ require 'chef/provisioning/convergence_strategy/install_cached'
7
+ require 'chef/provisioning/convergence_strategy/no_converge'
8
+
9
+ require 'yaml'
10
+ require 'docker/container'
11
+ require 'docker'
12
+
13
+ class Chef
14
+ module Provisioning
15
+ module DockerDriver
16
+ # Provisions machines using Docker
17
+ class Driver < Chef::Provisioning::Driver
18
+
19
+ include Chef::Mixin::ShellOut
20
+
21
+ attr_reader :connection
22
+
23
+ # URL scheme:
24
+ # docker:<path>
25
+ # canonical URL calls realpath on <path>
26
+ def self.from_url(driver_url, config)
27
+ Driver.new(driver_url, config)
28
+ end
29
+
30
+ def initialize(driver_url, config)
31
+ super
32
+ url = Driver.connection_url(driver_url)
33
+
34
+ if url
35
+ # Export this as it's expected
36
+ # to be set for command-line utilities
37
+ ENV['DOCKER_HOST'] = url
38
+ Chef::Log.debug("Setting Docker URL to #{url}")
39
+ Docker.url = url
40
+ end
41
+
42
+ @connection = Docker.connection
43
+ end
44
+
45
+ def self.canonicalize_url(driver_url, config)
46
+ url = Driver.connection_url(driver_url)
47
+ [ "docker:#{url}", config ]
48
+ end
49
+
50
+ # Parse the url from a URL, try to clean it up
51
+ # Returns a proper URL from the driver_url string. Examples include:
52
+ # docker:/var/run/docker.sock => unix:///var/run/docker.sock
53
+ # docker:192.168.0.1:1234 => tcp://192.168.0.1:1234
54
+ def self.connection_url(driver_url)
55
+ scheme, url = driver_url.split(':', 2)
56
+
57
+ if url && url.size > 0
58
+ # Clean up the URL with the protocol if needed (within reason)
59
+ case url
60
+ when /^\d+\.\d+\.\d+\.\d+:\d+$/
61
+ "tcp://#{url}"
62
+ when /^\//
63
+ "unix://#{url}"
64
+ when /^(tcp|unix)/
65
+ url
66
+ else
67
+ "tcp://#{url}"
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+ def allocate_machine(action_handler, machine_spec, machine_options)
74
+
75
+ container_name = machine_spec.name
76
+ machine_spec.location = {
77
+ 'driver_url' => driver_url,
78
+ 'driver_version' => Chef::Provisioning::DockerDriver::VERSION,
79
+ 'allocated_at' => Time.now.utc.to_s,
80
+ 'host_node' => action_handler.host_node,
81
+ 'container_name' => container_name,
82
+ 'image_id' => machine_options[:image_id]
83
+ }
84
+ end
85
+
86
+ def ready_machine(action_handler, machine_spec, machine_options)
87
+ base_image_name = build_container(machine_spec, machine_options)
88
+ start_machine(action_handler, machine_spec, machine_options, base_image_name)
89
+ machine_for(machine_spec, machine_options, base_image_name)
90
+ end
91
+
92
+ def build_container(machine_spec, machine_options)
93
+
94
+ docker_options = machine_options[:docker_options]
95
+
96
+ base_image = docker_options[:base_image]
97
+ source_name = base_image[:name]
98
+ source_repository = base_image[:repository]
99
+ source_tag = base_image[:tag]
100
+
101
+ # Don't do this if we're loading from an image
102
+ if docker_options[:from_image]
103
+ "#{source_repository}:#{source_tag}"
104
+ else
105
+ target_repository = 'chef'
106
+ target_tag = machine_spec.name
107
+
108
+ image = find_image(target_repository, target_tag)
109
+
110
+ # kick off image creation
111
+ if image == nil
112
+ Chef::Log.debug("No matching images for #{target_repository}:#{target_tag}, creating!")
113
+ image = Docker::Image.create('fromImage' => source_name,
114
+ 'repo' => source_repository ,
115
+ 'tag' => source_tag)
116
+ Chef::Log.debug("Allocated #{image}")
117
+ image.tag('repo' => 'chef', 'tag' => target_tag)
118
+ Chef::Log.debug("Tagged image #{image}")
119
+ end
120
+
121
+ "#{target_repository}:#{target_tag}"
122
+ end
123
+ end
124
+
125
+ def allocate_image(action_handler, image_spec, image_options, machine_spec)
126
+ # Set machine options on the image to match our newly created image
127
+ image_spec.machine_options = {
128
+ :docker_options => {
129
+ :base_image => {
130
+ :name => "chef_#{image_spec.name}",
131
+ :repository => 'chef',
132
+ :tag => image_spec.name
133
+ },
134
+ :from_image => true
135
+ }
136
+ }
137
+ end
138
+
139
+ def ready_image(action_handler, image_spec, image_options)
140
+ Chef::Log.debug('READY IMAGE!')
141
+ end
142
+
143
+ # Connect to machine without acquiring it
144
+ def connect_to_machine(machine_spec, machine_options)
145
+ Chef::Log.debug('Connect to machine!')
146
+ end
147
+
148
+ def destroy_machine(action_handler, machine_spec, machine_options)
149
+ container_name = machine_spec.location['container_name']
150
+ Chef::Log.debug("Destroying container: #{container_name}")
151
+ container = Docker::Container.get(container_name, @connection)
152
+
153
+ begin
154
+ Chef::Log.debug("Stopping #{container_name}")
155
+ container.stop
156
+ rescue Excon::Errors::NotModified
157
+ # this is okay
158
+ Chef::Log.debug('Already stopped!')
159
+ end
160
+
161
+ Chef::Log.debug("Removing #{container_name}")
162
+ container.delete
163
+
164
+ Chef::Log.debug("Destroying image: chef:#{container_name}")
165
+ image = Docker::Image.get("chef:#{container_name}")
166
+ image.delete
167
+
168
+ end
169
+
170
+ def stop_machine(action_handler, node)
171
+ Chef::Log.debug("Stop machine: #{node.inspect}")
172
+ end
173
+
174
+ def image_named(image_name)
175
+ Docker::Image.all.select {
176
+ |i| i.info['RepoTags'].include? image_name
177
+ }.first
178
+ end
179
+
180
+ def find_image(repository, tag)
181
+ Docker::Image.all.select {
182
+ |i| i.info['RepoTags'].include? "#{repository}:#{tag}"
183
+ }.first
184
+ end
185
+
186
+ def driver_url
187
+ "docker:#{Docker.url}"
188
+ end
189
+
190
+ def start_machine(action_handler, machine_spec, machine_options, base_image_name)
191
+ # Spin up a docker instance if needed, otherwise use the existing one
192
+ container_name = machine_spec.location['container_name']
193
+
194
+ begin
195
+ Docker::Container.get(container_name, @connection)
196
+ rescue Docker::Error::NotFoundError
197
+ docker_options = machine_options[:docker_options]
198
+ Chef::Log.debug("Start machine for container #{container_name} using base image #{base_image_name} with options #{docker_options.inspect}")
199
+ image = image_named(base_image_name)
200
+ container = Docker::Container.create('Image' => image.id, 'name' => container_name)
201
+ Chef::Log.debug("Container id: #{container.id}")
202
+ machine_spec.location['container_id'] = container.id
203
+ end
204
+
205
+ end
206
+
207
+ def machine_for(machine_spec, machine_options, base_image_name)
208
+ Chef::Log.debug('machine_for...')
209
+
210
+ docker_options = machine_options[:docker_options]
211
+
212
+ transport = DockerTransport.new(machine_spec.location['container_name'],
213
+ base_image_name,
214
+ nil,
215
+ Docker.connection)
216
+
217
+ convergence_strategy = if docker_options[:from_image]
218
+ Chef::Provisioning::ConvergenceStrategy::NoConverge.new({}, config)
219
+ else
220
+ convergence_strategy_for(machine_spec, machine_options)
221
+ end
222
+
223
+ Chef::Provisioning::DockerDriver::DockerContainerMachine.new(
224
+ machine_spec,
225
+ transport,
226
+ convergence_strategy,
227
+ :command => docker_options[:command],
228
+ :ports => [].push(docker_options[:ports]).flatten,
229
+ :keep_stdin_open => docker_options[:keep_stdin_open]
230
+ )
231
+ end
232
+
233
+ def convergence_strategy_for(machine_spec, machine_options)
234
+ @unix_convergence_strategy ||= begin
235
+ Chef::Provisioning::ConvergenceStrategy::InstallCached.
236
+ new(machine_options[:convergence_options], config)
237
+ end
238
+ end
239
+
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,7 @@
1
+ class Chef
2
+ module Provisioning
3
+ module DockerDriver
4
+ VERSION = '0.5'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ require 'chef/provisioning/docker_driver/driver'
2
+
3
+ ChefMetal.register_driver_class('docker', Chef::Provisioning::DockerDriver::Driver)
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chef-provisioning-docker
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.5'
5
+ platform: ruby
6
+ authors:
7
+ - Tom Duffield
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chef
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: chef-provisioning
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: docker-api
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sys-proctable
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Provisioner for creating Docker containers in Chef Provisioning.
98
+ email: tom@getchef.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files:
102
+ - README.md
103
+ - LICENSE
104
+ files:
105
+ - LICENSE
106
+ - README.md
107
+ - Rakefile
108
+ - lib/chef/provisioning/docker_driver.rb
109
+ - lib/chef/provisioning/docker_driver/chef_zero_http_proxy.rb
110
+ - lib/chef/provisioning/docker_driver/docker_container_machine.rb
111
+ - lib/chef/provisioning/docker_driver/docker_transport.rb
112
+ - lib/chef/provisioning/docker_driver/driver.rb
113
+ - lib/chef/provisioning/docker_driver/version.rb
114
+ - lib/chef/provisioning/driver_init/docker.rb
115
+ homepage: https://github.com/opscode/chef-provisioning-docker
116
+ licenses: []
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.2.2
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Provisioner for creating Docker containers in Chef Provisioning.
138
+ test_files: []
139
+ has_rdoc: