Skip to content

Commit

Permalink
Merge pull request #712 from scotje/641_pdk_bundle_passthru
Browse files Browse the repository at this point in the history
(PDK-641) Make `pdk bundle` fully interactive
  • Loading branch information
glennsarti committed Jul 30, 2019
2 parents 2c34863 + de03313 commit d6aabe1
Show file tree
Hide file tree
Showing 7 changed files with 539 additions and 217 deletions.
5 changes: 1 addition & 4 deletions lib/pdk/cli/bundle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,13 @@ module PDK::CLI

gemfile_env = PDK::Util::Bundler::BundleHelper.gemfile_env(puppet_env[:gemset])

command = PDK::CLI::Exec::Command.new(PDK::CLI::Exec.bundle_bin, *args).tap do |c|
command = PDK::CLI::Exec::InteractiveCommand.new(PDK::CLI::Exec.bundle_bin, *args).tap do |c|
c.context = :pwd
c.update_environment(gemfile_env)
end

result = command.execute!

$stderr.puts result[:stdout]
$stderr.puts result[:stderr]

exit result[:exit_code]
end
end
Expand Down
219 changes: 12 additions & 207 deletions lib/pdk/cli/exec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'bundler'
require 'childprocess'
require 'English'
require 'tempfile'
require 'tty-spinner'
require 'tty-which'
Expand All @@ -11,6 +12,9 @@
module PDK
module CLI
module Exec
require 'pdk/cli/exec/command'
require 'pdk/cli/exec/interactive_command'

def self.execute(*cmd)
Command.new(*cmd).execute!
end
Expand All @@ -19,6 +23,14 @@ def self.execute_with_env(env, *cmd)
Command.new(*cmd).tap { |c| c.environment = env }.execute!
end

def self.execute_interactive(*cmd)
InteractiveCommand.new(*cmd).execute!
end

def self.execute_interactive_with_env(env, *cmd)
InteractiveCommand.new(*cmd).tap { |c| c.environment = env }.execute!
end

def self.ensure_bin_present!(bin_path, bin_name)
message = _('Unable to find `%{name}`. Check that it is installed and try again.') % {
name: bin_name,
Expand Down Expand Up @@ -61,213 +73,6 @@ def self.try_vendored_bin(vendored_bin_path, fallback)
PDK.logger.debug(_("Using '%{vendored_bin}' from PDK package.") % { vendored_bin: vendored_bin_full_path })
vendored_bin_full_path
end

# TODO: decide how/when to connect stdin to child process for things like pry
# TODO: need a way to set callbacks on new stdout/stderr data
class Command
attr_reader :argv
attr_reader :context
attr_accessor :timeout
attr_accessor :environment
attr_writer :exec_group

def initialize(*argv)
@argv = argv

@process = ChildProcess.build(*@argv)
@process.leader = true

@stdout = Tempfile.new('stdout').tap { |io| io.sync = true }
@stderr = Tempfile.new('stderr').tap { |io| io.sync = true }

@process.io.stdout = @stdout
@process.io.stderr = @stderr

# Default to running things in the system context.
@context = :system

# Extra environment vars to add to base set.
@environment = {}

# Register the ExecGroup when running in parallel
@exec_group = nil
end

def context=(new_context)
unless [:system, :module, :pwd].include?(new_context)
raise ArgumentError, _("Expected execution context to be :system or :module but got '%{context}'.") % { context: new_context }
end

@context = new_context
end

def register_spinner(spinner, opts = {})
return unless PDK::CLI::Util.interactive?
@success_message = opts.delete(:success)
@failure_message = opts.delete(:failure)

@spinner = spinner
end

def add_spinner(message, opts = {})
return unless PDK::CLI::Util.interactive?
@success_message = opts.delete(:success)
@failure_message = opts.delete(:failure)

@spinner = TTY::Spinner.new("[:spinner] #{message}", opts.merge(PDK::CLI::Util.spinner_opts_for_platform))
end

def update_environment(additional_env)
@environment.merge!(additional_env)
end

def check_for_legacy_env_vars
if ENV['PUPPET_GEM_VERSION']
PDK.logger.warn_once _(
'PUPPET_GEM_VERSION is not supported by PDK. ' \
'Use the --puppet-version option on your PDK command ' \
'or set the PDK_PUPPET_VERSION environment variable instead',
)
@process.environment['PUPPET_GEM_VERSION'] = nil
end

%w[FACTER HIERA].each do |gem|
if ENV["#{gem}_GEM_VERSION"]
PDK.logger.warn_once _('%{varname} is not supported by PDK.') % { varname: "#{gem}_GEM_VERSION" }
@process.environment["#{gem}_GEM_VERSION"] = nil
end
end
end

def execute!
# Start spinning if configured.
@spinner.auto_spin if @spinner

# Add custom env vars.
@environment.each do |k, v|
@process.environment[k] = v
end

check_for_legacy_env_vars

@process.environment['BUNDLE_IGNORE_CONFIG'] = '1'

if [:module, :pwd].include?(context)
@process.environment['GEM_HOME'] = PDK::Util::RubyVersion.gem_home
@process.environment['GEM_PATH'] = PDK::Util::RubyVersion.gem_path

# Make sure invocation of Ruby prefers our private installation.
package_binpath = PDK::Util.package_install? ? File.join(PDK::Util.pdk_package_basedir, 'bin') : nil
@process.environment['PATH'] = [
PDK::Util::RubyVersion.bin_path,
File.join(@process.environment['GEM_HOME'], 'bin'),
PDK::Util::RubyVersion.gem_paths_raw.map { |gem_path| File.join(gem_path, 'bin') },
package_binpath,
PDK::Util.package_install? ? PDK::Util::Git.git_paths : nil,
ENV['PATH'],
].compact.flatten.join(File::PATH_SEPARATOR)

mod_root = PDK::Util.module_root

unless mod_root
@spinner.error if @spinner

raise PDK::CLI::FatalError, _('Current working directory is not part of a module. (No metadata.json was found.)')
end

if Dir.pwd == mod_root || context == :pwd
run_process_in_clean_env!
else
Dir.chdir(mod_root) do
run_process_in_clean_env!
end
end
else
run_process!
end

# Stop spinning when done (if configured).
stop_spinner

@stdout.rewind
@stderr.rewind

process_data = {
stdout: @stdout.read,
stderr: @stderr.read,
exit_code: @process.exit_code,
duration: @duration,
}

PDK.logger.debug _('STDOUT: %{output}') % {
output: process_data[:stdout].empty? ? 'N/A' : "\n#{process_data[:stdout]}",
}
PDK.logger.debug _('STDERR: %{output}') % {
output: process_data[:stderr].empty? ? 'N/A' : "\n#{process_data[:stderr]}",
}

process_data
ensure
@stdout.close
@stderr.close
end

protected

def stop_spinner
return unless @spinner

# If it is a single spinner, we need to send it a success/error message
if @process.exit_code.zero?
@spinner.success(@success_message || '')
else
@spinner.error(@failure_message || '')
end
end

def run_process_in_clean_env!
::Bundler.with_clean_env do
run_process!
end
end

def run_process!
command_string = argv.join(' ')

PDK.logger.debug(_("Executing '%{command}'") % { command: command_string })

if context == :module
PDK.logger.debug(_('Command environment:'))
@process.environment.each do |var, val|
PDK.logger.debug(" #{var}: #{val}")
end
end

start_time = Time.now

begin
@process.start
rescue ChildProcess::LaunchError => e
raise PDK::CLI::FatalError, _("Failed to execute '%{command}': %{message}") % { command: command_string, message: e.message }
end

if timeout
begin
@process.poll_for_exit(timeout)
rescue ChildProcess::TimeoutError
@process.stop # tries increasingly harsher methods to kill the process.
end
else
# Wait indfinitely if no timeout set.
@process.wait
end

@duration = Time.now - start_time

PDK.logger.debug(_("Execution of '%{command}' complete (duration: %{duration_in_seconds}s; exit code: %{exit_code})") %
{ command: command_string, duration_in_seconds: @duration, exit_code: @process.exit_code })
end
end
end
end
end
Loading

0 comments on commit d6aabe1

Please sign in to comment.