From ef18b457869f1dad5d629a6edb14008b2aa3adbe Mon Sep 17 00:00:00 2001 From: Rui Lopes Date: Mon, 31 May 2021 20:20:12 +0100 Subject: [PATCH] ensure that the shell provisioner only emits complete output lines to the ui partial lines are buffered until they are complete (have a line ending) see https://github.com/hashicorp/vagrant/issues/11047 --- lib/vagrant/util/line_buffer.rb | 35 ++++++++++ plugins/provisioners/shell/provisioner.rb | 78 ++++++++++++++++------- 2 files changed, 91 insertions(+), 22 deletions(-) create mode 100644 lib/vagrant/util/line_buffer.rb diff --git a/lib/vagrant/util/line_buffer.rb b/lib/vagrant/util/line_buffer.rb new file mode 100644 index 000000000..250c4749a --- /dev/null +++ b/lib/vagrant/util/line_buffer.rb @@ -0,0 +1,35 @@ +require 'stringio' + +module Vagrant + module Util + class LineBuffer + def initialize + @buffer = StringIO.new + end + + def lines(data, &block) + if data == nil + return + end + remaining_buffer = StringIO.new + @buffer << data + @buffer.string.each_line do |line| + if line.end_with? "\n" + block.call(line.rstrip) + else + remaining_buffer << line + break + end + end + @buffer = remaining_buffer + end + + def remaining(&block) + if @buffer.length > 0 + block.call(@buffer.string.rstrip) + @buffer = StringIO.new + end + end + end + end +end diff --git a/plugins/provisioners/shell/provisioner.rb b/plugins/provisioners/shell/provisioner.rb index 53800e254..51aa662a4 100644 --- a/plugins/provisioners/shell/provisioner.rb +++ b/plugins/provisioners/shell/provisioner.rb @@ -2,6 +2,7 @@ require "pathname" require "tempfile" require "vagrant/util/downloader" +require "vagrant/util/line_buffer" require "vagrant/util/retryable" module VagrantPlugins @@ -65,15 +66,42 @@ module VagrantPlugins protected - # This handles outputting the communication data back to the UI - def handle_comm(type, data) - if [:stderr, :stdout].include?(type) - # Output the data with the proper color based on the stream. - color = type == :stdout ? :green : :red + # This buffers a communicator output data (which can have partial + # lines) into full lines and emits them back to the UI. + def comm_line_buffer(&block) + stdout = Vagrant::Util::LineBuffer.new + stderr = Vagrant::Util::LineBuffer.new - # Clear out the newline since we add one - data = data.chomp - return if data.empty? + line_buffer_handle_comm = Proc.new do |type, data| + case type + when :stdout + stdout.lines data do |line| + handle_comm(type, line) + end + when :stderr + stderr.lines data do |line| + handle_comm(type, line) + end + end + end + + # execute the block and emit complete lines to the UI. + block.call(line_buffer_handle_comm) + + # emit the remaining as incomplete/partial lines to the UI. + stdout.remaining do |line| + handle_comm(:stdout, line) + end + stderr.remaining do |line| + handle_comm(:stderr, line) + end + end + + # This handles outputting the communication line back to the UI + def handle_comm(type, line) + if [:stderr, :stdout].include?(type) + # Output the line with the proper color based on the stream. + color = type == :stdout ? :green : :red options = {} options[:color] = color if !config.keep_color @@ -121,12 +149,14 @@ module VagrantPlugins end # Execute it with sudo - comm.execute( - command, - sudo: config.privileged, - error_key: :ssh_bad_exit_status_muted - ) do |type, data| - handle_comm(type, data) + comm_line_buffer do |handle_comm| + comm.execute( + command, + sudo: config.privileged, + error_key: :ssh_bad_exit_status_muted + ) do |type, data| + handle_comm.call(type, data) + end end end end @@ -176,12 +206,14 @@ module VagrantPlugins end # Execute it with sudo - comm.execute( - command, - shell: :powershell, - error_key: :ssh_bad_exit_status_muted - ) do |type, data| - handle_comm(type, data) + comm_line_buffer do |handle_comm| + comm.execute( + command, + shell: :powershell, + error_key: :ssh_bad_exit_status_muted + ) do |type, data| + handle_comm.call(type, data) + end end end end @@ -245,8 +277,10 @@ module VagrantPlugins end # Execute it with sudo - comm.sudo(command, { elevated: config.privileged, interactive: config.powershell_elevated_interactive }) do |type, data| - handle_comm(type, data) + comm_line_buffer do |handle_comm| + comm.sudo(command, { elevated: config.privileged, interactive: config.powershell_elevated_interactive }) do |type, data| + handle_comm.call(type, data) + end end end end