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

Commit

Permalink
Merge pull request #56 from mwrock/execute
Browse files Browse the repository at this point in the history
leverage exec for transport and implements connect_to_machine and stop_machine
  • Loading branch information
tyler-ball committed Jul 28, 2015
2 parents 265d041 + 977ade1 commit 06a352a
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 305 deletions.
13 changes: 0 additions & 13 deletions .gitignore
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
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
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'])
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
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

0 comments on commit 06a352a

Please sign in to comment.