2011-11-03 04:38:48 +00:00
|
|
|
require "fileutils"
|
2011-11-03 06:16:29 +00:00
|
|
|
require "pathname"
|
2011-11-03 04:38:48 +00:00
|
|
|
|
2011-11-03 04:09:38 +00:00
|
|
|
require "log4r"
|
|
|
|
require "posix-spawn"
|
|
|
|
|
|
|
|
require File.expand_path("../tempdir", __FILE__)
|
|
|
|
|
|
|
|
module Acceptance
|
|
|
|
# This class manages an isolated environment for Vagrant to
|
|
|
|
# run in. It creates a temporary directory to act as the
|
|
|
|
# working directory as well as sets a custom home directory.
|
|
|
|
class IsolatedEnvironment
|
|
|
|
include POSIX::Spawn
|
|
|
|
|
2011-11-03 04:41:41 +00:00
|
|
|
attr_reader :homedir
|
|
|
|
attr_reader :workdir
|
|
|
|
|
2011-11-03 04:09:38 +00:00
|
|
|
# Initializes an isolated environment. You can pass in some
|
|
|
|
# options here to configure runing custom applications in place
|
|
|
|
# of others as well as specifying environmental variables.
|
|
|
|
#
|
|
|
|
# @param [Hash] apps A mapping of application name (such as "vagrant")
|
|
|
|
# to an alternate full path to the binary to run.
|
|
|
|
# @param [Hash] env Additional environmental variables to inject
|
|
|
|
# into the execution environments.
|
|
|
|
def initialize(apps=nil, env=nil)
|
|
|
|
@logger = Log4r::Logger.new("acceptance::isolated_environment")
|
|
|
|
|
|
|
|
@apps = apps || {}
|
|
|
|
@env = env || {}
|
|
|
|
|
|
|
|
# Create a temporary directory for our work
|
|
|
|
@tempdir = Tempdir.new("vagrant")
|
|
|
|
@logger.info("Initialize isolated environment: #{@tempdir.path}")
|
2011-11-03 04:38:48 +00:00
|
|
|
|
|
|
|
# Setup the home and working directories
|
2011-11-03 06:16:29 +00:00
|
|
|
@homedir = Pathname.new(File.join(@tempdir.path, "home"))
|
|
|
|
@workdir = Pathname.new(File.join(@tempdir.path, "work"))
|
2011-11-03 04:38:48 +00:00
|
|
|
|
2011-11-03 06:16:29 +00:00
|
|
|
@homedir.mkdir
|
|
|
|
@workdir.mkdir
|
2011-11-03 04:38:48 +00:00
|
|
|
|
2011-11-03 06:16:29 +00:00
|
|
|
@env["HOME"] = @homedir.to_s
|
2011-11-03 04:09:38 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Executes a command in the context of this isolated environment.
|
|
|
|
# Any command executed will therefore see our temporary directory
|
|
|
|
# as the home directory.
|
|
|
|
def execute(command, *argN)
|
2011-11-03 06:16:29 +00:00
|
|
|
command = replace_command(command)
|
2011-11-03 04:09:38 +00:00
|
|
|
|
2011-11-04 05:12:51 +00:00
|
|
|
# Add our hash options to the arguments list
|
|
|
|
argN << { :chdir => @workdir.to_s }
|
|
|
|
|
2011-11-03 04:09:38 +00:00
|
|
|
# Execute in a separate process, wait for it to complete, and
|
|
|
|
# return the IO streams.
|
2011-11-05 21:44:24 +00:00
|
|
|
@logger.info("Executing: #{command} #{argN.inspect}. Output will stream in...")
|
2011-11-04 05:12:51 +00:00
|
|
|
pid, stdin, stdout, stderr = popen4(@env, command, *argN)
|
2011-11-05 21:44:24 +00:00
|
|
|
|
|
|
|
io_data = {
|
|
|
|
stdout => "",
|
|
|
|
stderr => ""
|
|
|
|
}
|
|
|
|
|
|
|
|
while results = IO.select([stdout, stderr], nil, nil, 5)
|
|
|
|
rs = results[0]
|
|
|
|
next if rs.empty?
|
|
|
|
|
|
|
|
rs.each do |r|
|
|
|
|
data = r.readline
|
|
|
|
io_data[r] += data
|
|
|
|
|
|
|
|
io_name = r == stdout ? "stdout" : "stderr"
|
|
|
|
@logger.debug("[#{io_name}] #{data.chomp}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-11-03 04:09:38 +00:00
|
|
|
_pid, status = Process.waitpid2(pid)
|
2011-11-05 21:44:24 +00:00
|
|
|
@logger.debug("Exit status: #{status.exitstatus}")
|
2011-11-03 04:09:38 +00:00
|
|
|
|
2011-11-05 21:44:24 +00:00
|
|
|
return ExecuteProcess.new(status.exitstatus, io_data[stdout], io_data[stderr])
|
2011-11-03 04:09:38 +00:00
|
|
|
end
|
2011-11-03 04:41:41 +00:00
|
|
|
|
|
|
|
# Closes the environment, cleans up the temporary directories, etc.
|
|
|
|
def close
|
|
|
|
# Delete the temporary directory
|
2011-11-05 21:44:24 +00:00
|
|
|
@logger.info("Removing isolated environment: #{@tempdir.path}")
|
2011-11-03 04:41:41 +00:00
|
|
|
FileUtils.rm_rf(@tempdir.path)
|
|
|
|
end
|
2011-11-03 06:16:29 +00:00
|
|
|
|
|
|
|
# This replaces a command with a replacement defined when this
|
|
|
|
# isolated environment was initialized. If nothing was defined,
|
|
|
|
# then the command itself is returned.
|
|
|
|
def replace_command(command)
|
|
|
|
return @apps[command] if @apps.has_key?(command)
|
|
|
|
return command
|
|
|
|
end
|
2011-11-03 04:09:38 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# This class represents a process which has run via the IsolatedEnvironment.
|
|
|
|
# This is a readonly structure that can be used to inspect the exit status,
|
|
|
|
# stdout, stderr, etc. from the process which ran.
|
|
|
|
class ExecuteProcess
|
|
|
|
attr_reader :exit_status
|
|
|
|
attr_reader :stdout
|
|
|
|
attr_reader :stderr
|
|
|
|
|
|
|
|
def initialize(exit_status, stdout, stderr)
|
|
|
|
@exit_status = exit_status
|
|
|
|
@stdout = stdout
|
|
|
|
@stderr = stderr
|
|
|
|
end
|
2011-11-04 04:38:15 +00:00
|
|
|
|
|
|
|
def success?
|
|
|
|
@exit_status == 0
|
|
|
|
end
|
2011-11-03 04:09:38 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|