Skip to content
This repository has been archived by the owner on Jul 6, 2018. It is now read-only.

leverage exec for transport and implements connect_to_machine and stop_machine #56

Merged
merged 2 commits into from
Jul 28, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,3 @@ spec/reports
test/tmp
test/version_tmp
tmp
*.bundle
*.so
*.o
*.a
mkmf.log
*.sw*
clients
nodes
docs/examples/clients
docs/examples/nodes
docs/examples/data_bags
x.rb
.idea/
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ This supports the new machine image paradigm; with Docker you can build a base
```ruby
require 'chef/provisioning/docker_driver'

machine_image 'web_server' do
recipe 'apache'
machine_image 'ssh_server' do
recipe 'openssh'

machine_options :docker_options => {
:base_image => {
Expand All @@ -84,11 +84,12 @@ machine_image 'web_server' do
}
end

machine 'web00' do
from_image 'web_server'
machine 'ssh00' do
from_image 'ssh_server'

machine_options :docker_options => {
:command => '/usr/sbin/httpd'
:command => '/usr/sbin/sshd -D -o UsePAM=no -o UsePrivilegeSeparation=no -o PidFile=/tmp/sshd.pid',
:ports => [22]
}
end
```
Expand Down
31 changes: 16 additions & 15 deletions lib/chef/provisioning/docker_driver/docker_container_machine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,30 @@ class DockerContainerMachine < Chef::Provisioning::Machine::UnixMachine
# Options is expected to contain the optional keys
# :command => the final command to execute
# :ports => a list of port numbers to listen on
def initialize(machine_spec, transport, convergence_strategy, opts = {})
def initialize(machine_spec, transport, convergence_strategy, command = nil)
super(machine_spec, transport, convergence_strategy)
@env = opts[:env]
@command = opts[:command]
@ports = opts[:ports]
@volumes = opts[:volumes]
@keep_stdin_open = opts[:keep_stdin_open]
@container_name = machine_spec.location['container_name']
@command = command
@transport = transport
end

def execute_always(command, options = {})
transport.execute(command, { :read_only => true }.merge(options))
end

def converge(action_handler)
super action_handler
if @command
Chef::Log.debug("DockerContainerMachine converge complete, executing #{@command} in #{@container_name}")
@transport.execute(@command, :env => @env ,:detached => true, :read_only => true, :ports => @ports, :volumes => @volumes, :keep_stdin_open => @keep_stdin_open)
Chef::Log.debug("DockerContainerMachine converge complete, executing #{@command} in #{@container_name}")
image = transport.container.commit(
'repo' => 'chef',
'tag' => machine_spec.reference['container_name']
)
machine_spec.reference['image_id'] = image.id

if @command && transport.container.info['Config']['Cmd'].join(' ') != @command
transport.container.delete(:force => true)
container = image.run(Shellwords.split(@command))
container.rename(machine_spec.reference['container_name'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think

container = Docker::Container.create('Image' => image.id, 'Cmd' => Shellwords.split(@command), 'name' => container_name)

more convenient way to run container.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its kind of a wash since create doesn't start the container so either way you have to perform 2 operations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it does https://github.com/swipely/docker-api/blob/master/lib/docker/container.rb#L227 . Anyways it's minor and totally up to you.

machine_spec.reference['container_id'] = container.id
transport.container = container
end
machine_spec.save(action_handler)
end

end
end
end
Expand Down
181 changes: 20 additions & 161 deletions lib/chef/provisioning/docker_driver/docker_transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,117 +12,38 @@ class Chef
module Provisioning
module DockerDriver
class DockerTransport < Chef::Provisioning::Transport
def initialize(container_name, base_image_name, credentials, connection, tunnel_transport = nil)
@repository_name = 'chef'
@container_name = container_name
@image = Docker::Image.get(base_image_name, connection)
@credentials = credentials
@connection = connection
@tunnel_transport = tunnel_transport
def initialize(container, config)
@container = container
@config = config
end

include Chef::Mixin::ShellOut
attr_reader :config
attr_accessor :container

attr_reader :container_name
attr_reader :repository_name
attr_reader :image
attr_reader :credentials
attr_reader :connection
attr_reader :tunnel_transport

# Execute the specified command inside the container, returns a Mixlib::Shellout object
# Options contains the optional keys:
# :env => env vars
# :read_only => Do not commit this execute operation, just execute it
# :ports => ports to listen on (-p command-line options)
# :detached => true/false, execute this command in detached mode (for final program to run)
def execute(command, options={})
Chef::Log.debug("execute '#{command}' with options #{options}")

begin
connection.post("/containers/#{container_name}/stop?t=0", '')
Chef::Log.debug("stopped /containers/#{container_name}")
rescue Excon::Errors::NotModified
Chef::Log.debug("Already stopped #{container_name}")
rescue Docker::Error::NotFoundError
end

begin
# Delete the container if it exists and is dormant
connection.delete("/containers/#{container_name}?v=true&force=true")
Chef::Log.debug("deleted /containers/#{container_name}")
rescue Docker::Error::NotFoundError
opts = {}
if options[:keep_stdin_open]
opts[:stdin] = true
end

command = Shellwords.split(command) if command.is_a?(String)

# TODO shell_out has no way to live stream stderr???
live_stream = nil
live_stream = STDOUT if options[:stream]
live_stream = options[:stream_stdout] if options[:stream_stdout]

args = ['docker', 'run', '--name', container_name]

if options[:env]
options[:env].each do |key, value|
args << '-e'
args << "#{key}=#{value}"
end
end

if options[:detached]
args << '--detach'
end

if options[:ports]
options[:ports].each do |portnum|
args << '-p'
args << "#{portnum}"
end
end

if options[:volumes]
options[:volumes].each do |volume|
args << '-v'
args << "#{volume}"
response = container.exec(command, opts) do |stream, chunk|
case stream
when :stdout
stream_chunk(options, chunk, nil)
when :stderr
stream_chunk(options, nil, chunk)
end
end

if options[:keep_stdin_open]
args << '-i'
end

args << @image.id
args += command
Chef::Log.debug("Execute complete: status #{response[2]}")

cmdstr = Shellwords.join(args)
Chef::Log.debug("Executing #{cmdstr}")

# Remove this when https://github.com/opscode/chef/pull/2100 gets merged and released
# nullify live_stream because at the moment EventsOutputStream doesn't understand <<, which
# ShellOut uses
live_stream = nil unless live_stream.respond_to? :<<

cmd = Mixlib::ShellOut.new(cmdstr, :live_stream => live_stream, :timeout => execute_timeout(options))

cmd.run_command

unless options[:read_only]
Chef::Log.debug("Committing #{container_name} as #{repository_name}:#{container_name}")
container = Docker::Container.get(container_name)
@image = container.commit('repo' => repository_name, 'tag' => container_name)
end

Chef::Log.debug("Execute complete: status #{cmd.exitstatus}")

cmd
DockerResult.new(command.join(' '), options, response[0].join, response[1].join, response[2])
end

def read_file(path)
container = Docker::Container.create({
'Image' => @image.id,
'Cmd' => %w(echo true)
}, connection)
begin
tarfile = ''
# NOTE: this would be more efficient if we made it a stream and passed that to Minitar
Expand All @@ -135,8 +56,6 @@ def read_file(path)
else
raise
end
ensure
container.delete
end

output = ''
Expand All @@ -153,12 +72,7 @@ def read_file(path)
end

def write_file(path, content)
# TODO hate tempfiles. Find an in memory way.
Tempfile.open('metal_docker_write_file') do |file|
file.write(content)
file.close
@image = @image.insert_local('localPath' => file.path, 'outputPath' => path, 't' => "#{repository_name}:#{container_name}")
end
File.open(container_path(path), 'w') { |file| file.write(content) }
end

def download_file(path, local_path)
Expand All @@ -173,7 +87,7 @@ def download_file(path, local_path)
end

def upload_file(local_path, path)
@image = @image.insert_local('localPath' => local_path, 'outputPath' => path, 't' => "#{repository_name}:#{container_name}")
FileUtils.cp(local_path, container_path(path))
end

def make_url_available_to_remote(url)
Expand Down Expand Up @@ -236,40 +150,8 @@ def using_boot2docker?
end
end

# Copy of container.attach with timeout support and pipeline
def attach_with_timeout(container, read_timeout, options = {}, &block)
opts = {
:stream => true, :stdout => true, :stderr => true
}.merge(options)
# Creates list to store stdout and stderr messages
msgs = Docker::Messages.new
connection.start_request(
:post,
"/containers/#{container.id}/attach",
opts,
:response_block => attach_for(block, msgs),
:read_timeout => read_timeout,
:pipeline => true,
:persistent => true
)
end

# Method that takes chunks and calls the attached block for each mux'd message
def attach_for(block, msg_stack)
messages = Docker::Messages.new
lambda do |c,r,t|
messages = messages.decipher_messages(c)
msg_stack.append(messages)

unless block.nil?
messages.stdout_messages.each do |msg|
block.call(:stdout, msg)
end
messages.stderr_messages.each do |msg|
block.call(:stderr, msg)
end
end
end
def container_path(path)
File.join('proc', container.info['State']['Pid'].to_s, 'root', path)
end

class DockerResult
Expand Down Expand Up @@ -300,26 +182,3 @@ def error!
end
end
end

class Docker::Connection
def start_request(method, *args, &block)
request = compile_request_params(method, *args, &block)
if Docker.logger
Docker.logger.debug(
[request[:method], request[:path], request[:query], request[:body]]
)
end
excon = resource
[ excon, excon.request(request) ]
rescue Excon::Errors::BadRequest => ex
raise ClientError, ex.message
rescue Excon::Errors::Unauthorized => ex
raise UnauthorizedError, ex.message
rescue Excon::Errors::NotFound => ex
raise NotFoundError, ex.message
rescue Excon::Errors::InternalServerError => ex
raise ServerError, ex.message
rescue Excon::Errors::Timeout => ex
raise TimeoutError, ex.message
end
end
Loading