There was an issue before where the stdin buffer would always have space so it would always yield that block and Ruby would spin at 100%. Now we require all callers to say what they want to listen for. This drops CPU down to almost nothing. See GH-832
159 lines
5.3 KiB
Ruby
159 lines
5.3 KiB
Ruby
require "ostruct"
|
|
require "tempfile"
|
|
|
|
require "log4r"
|
|
|
|
require "vagrant/util/subprocess"
|
|
|
|
module Vagrant
|
|
module Easy
|
|
# This class contains all the "operations" that easy commands are able
|
|
# to run. An instance of this is class is what is sent into the callback
|
|
# for all easy commands.
|
|
class Operations
|
|
def initialize(vm)
|
|
@logger = Log4r::Logger.new("vagrant::easy::operations")
|
|
@vm = vm
|
|
end
|
|
|
|
# Download a file from the remote virtual machine. If `to` is nil, then
|
|
# the contents are returned as a string.
|
|
#
|
|
# @param [String] from Path on the remote VM to download.
|
|
# @param [String] to Path to a local file to save to.
|
|
# @return [String] File contents if `to` is nil.
|
|
def download(from, to=nil)
|
|
raise Errors::VMNotRunningError if @vm.state != :running
|
|
|
|
@logger.info("download: #{from} => #{to}")
|
|
@vm.channel.download(from, to)
|
|
end
|
|
|
|
# Runs a command on the local machine. This will return an object where
|
|
# you can access the `exit_code`, `stdout`, and `stderr` easiy:
|
|
#
|
|
# output = local("echo foo")
|
|
# puts "Output was #{output.stdout}"
|
|
#
|
|
# (Likewise, `exit_code` and `stderr` are attributes on the return value)
|
|
#
|
|
# It is recommended you use this `local` method rather than trying to
|
|
# manually use Ruby's underlying subprocess tools because this will use
|
|
# the Vagrant `Subprocess` class which has been refined over the years
|
|
# to work equally well on Windows, Mac OS X, Linux as well as on many
|
|
# runtimes such as CRuby and JRuby.
|
|
#
|
|
# @param [String] command Command to run
|
|
# @param [Hash] options Additional options
|
|
def local(command, options=nil)
|
|
@logger.info("local: #{command}")
|
|
|
|
block = nil
|
|
options = { :echo => true }.merge(options || {})
|
|
|
|
if options[:echo]
|
|
# If we're echoing data, then setup the block that sends the
|
|
# data to the UI.
|
|
block = Proc.new do |type, data|
|
|
if type == :stdout || type == :stderr
|
|
@vm.ui.info(data.to_s,
|
|
:prefix => false,
|
|
:new_line => false)
|
|
end
|
|
end
|
|
end
|
|
|
|
Vagrant::Util::Subprocess.execute(command, :notify => [:stdout, :stderr], &block)
|
|
end
|
|
|
|
# Run a shell command within the VM. The command will run within a
|
|
# shell environment, and the output and exit code will be returned
|
|
# as an object with attributes: `exit_code, `stdout`, and `stderr`.
|
|
# Example:
|
|
#
|
|
# output = run("echo foo")
|
|
# puts "Output was #{output.stdout}"
|
|
#
|
|
# @param [String] command Command to run
|
|
# @param [Hash] options Additional options
|
|
def run(command, options=nil)
|
|
@logger.info("run: #{command}")
|
|
options = { :echo => true }.merge(options || {})
|
|
remote_command(:execute, command, options)
|
|
end
|
|
|
|
# Same as {run} except runs the command with superuser privileges
|
|
# via `sudo`.
|
|
#
|
|
# @param [String] command Command
|
|
# @param [Hash] options Additional options
|
|
def sudo(command, options=nil)
|
|
@logger.info("sudo: #{command}")
|
|
options = { :echo => true }.merge(options || {})
|
|
remote_command(:sudo, command, options)
|
|
end
|
|
|
|
# Uploads a file to the virtual machine.
|
|
#
|
|
# Note that the `to` path must have proper permissions setup so that the
|
|
# SSH user can upload to it. If it does not, then you should upload
|
|
# to a location you do have permission to, then use {#sudo} to move
|
|
# it.
|
|
#
|
|
# @param [String] from Path to a local file or an IO object.
|
|
# @param [String] to Path where to upload to.
|
|
def upload(from, to)
|
|
raise Errors::VMNotRunningError if @vm.state != :running
|
|
|
|
# If we're dealing with an IO object, then save it to a temporary
|
|
# file and upload that. We define `temp = nil` here so that it
|
|
# doesn't go out of scope and get GC'd until after the method
|
|
# closes.
|
|
temp = nil
|
|
if from.is_a?(IO)
|
|
temp = Tempfile.new("vagrant")
|
|
temp.write(from)
|
|
temp.close
|
|
|
|
from = temp.path
|
|
end
|
|
|
|
# Perform the upload
|
|
@logger.info("upload: #{from} => #{to}")
|
|
@vm.channel.upload(from, to)
|
|
end
|
|
|
|
protected
|
|
|
|
# Runs a command on the remote host.
|
|
def remote_command(type, command, options)
|
|
# If the VM is not running, then we can't run SSH commands...
|
|
raise Errors::VMNotRunningError if @vm.state != :running
|
|
|
|
# Initialize the result object, execute, and store the data
|
|
result = OpenStruct.new
|
|
result.stderr = ""
|
|
result.stdout = ""
|
|
result.exit_code = @vm.channel.send(type, command,
|
|
:error_check => false) do |type, data|
|
|
if type == :stdout
|
|
result.stdout += data
|
|
elsif type == :stderr
|
|
result.stderr += data
|
|
end
|
|
|
|
# If we're echoing data, then echo it!
|
|
if options[:echo]
|
|
@vm.ui.info(data.to_s,
|
|
:prefix => false,
|
|
:new_line => false)
|
|
end
|
|
end
|
|
|
|
# Return the result
|
|
result
|
|
end
|
|
end
|
|
end
|
|
end
|