mirror of
https://github.com/hashicorp/vagrant.git
synced 2026-05-28 04:36:05 -04:00
Remove customized require behaviors and modify the bin executable to check for missing tools that Vagrant expects to exist when running outside of an installer.
230 lines
7.3 KiB
Ruby
230 lines
7.3 KiB
Ruby
# Copyright (c) HashiCorp, Inc.
|
|
# SPDX-License-Identifier: BUSL-1.1
|
|
|
|
require "log4r"
|
|
require "tempfile"
|
|
require "timeout"
|
|
|
|
require_relative "helper"
|
|
require_relative "shell"
|
|
require_relative "command_filter"
|
|
|
|
module VagrantPlugins
|
|
module CommunicatorWinRM
|
|
# Provides communication channel for Vagrant commands via WinRM.
|
|
class Communicator < Vagrant.plugin("2", :communicator)
|
|
include Vagrant::Util
|
|
|
|
def self.match?(machine)
|
|
# This is useless, and will likely be removed in the future (this
|
|
# whole method).
|
|
true
|
|
end
|
|
|
|
def initialize(machine)
|
|
@cmd_filter = CommandFilter.new()
|
|
@logger = Log4r::Logger.new("vagrant::communication::winrm")
|
|
@machine = machine
|
|
@shell = nil
|
|
|
|
@logger.info("Initializing WinRMCommunicator")
|
|
end
|
|
|
|
def wait_for_ready(timeout)
|
|
Timeout.timeout(timeout) do
|
|
# Wait for winrm_info to be ready
|
|
winrm_info = nil
|
|
while true
|
|
winrm_info = nil
|
|
begin
|
|
winrm_info = Helper.winrm_info(@machine)
|
|
rescue Errors::WinRMNotReady
|
|
@logger.debug("WinRM not ready yet; retrying until boot_timeout is reached.")
|
|
end
|
|
break if winrm_info
|
|
sleep 0.5
|
|
end
|
|
|
|
# Got it! Let the user know what we're connecting to.
|
|
@machine.ui.detail("WinRM address: #{shell.host}:#{shell.port}")
|
|
@machine.ui.detail("WinRM username: #{shell.username}")
|
|
@machine.ui.detail("WinRM execution_time_limit: #{shell.execution_time_limit}")
|
|
@machine.ui.detail("WinRM transport: #{shell.config.transport}")
|
|
|
|
last_message = nil
|
|
last_message_repeat_at = 0
|
|
while true
|
|
message = nil
|
|
begin
|
|
begin
|
|
return true if ready?
|
|
rescue Vagrant::Errors::VagrantError => e
|
|
@logger.info("WinRM not ready: #{e.inspect}")
|
|
raise
|
|
end
|
|
rescue Errors::ConnectionTimeout
|
|
message = "Connection timeout."
|
|
rescue Errors::AuthenticationFailed
|
|
message = "Authentication failure."
|
|
rescue Errors::Disconnected
|
|
message = "Remote connection disconnect."
|
|
rescue Errors::ConnectionRefused
|
|
message = "Connection refused."
|
|
rescue Errors::ConnectionReset
|
|
message = "Connection reset."
|
|
rescue Errors::HostDown
|
|
message = "Host appears down."
|
|
rescue Errors::NoRoute
|
|
message = "Host unreachable."
|
|
rescue Errors::TransientError => e
|
|
# Any other retryable errors
|
|
message = e.message
|
|
end
|
|
|
|
# If we have a message to show, then show it. We don't show
|
|
# repeated messages unless they've been repeating longer than
|
|
# 10 seconds.
|
|
if message
|
|
message_at = Time.now.to_f
|
|
show_message = true
|
|
if last_message == message
|
|
show_message = (message_at - last_message_repeat_at) > 10.0
|
|
end
|
|
|
|
if show_message
|
|
@machine.ui.detail("Warning: #{message} Retrying...")
|
|
last_message = message
|
|
last_message_repeat_at = message_at
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue Timeout::Error
|
|
return false
|
|
end
|
|
|
|
def ready?
|
|
@logger.info("Checking whether WinRM is ready...")
|
|
|
|
result = Timeout.timeout(@machine.config.winrm.timeout) do
|
|
shell(true).cmd("hostname")
|
|
end
|
|
|
|
@logger.info("WinRM is ready!")
|
|
return true
|
|
rescue Errors::TransientError, VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady => e
|
|
# We catch a `TransientError` which would signal that something went
|
|
# that might work if we wait and retry.
|
|
@logger.info("WinRM not up: #{e.inspect}")
|
|
|
|
# We reset the shell to trigger calling of winrm_finder again.
|
|
# This resolves a problem when using vSphere where the winrm_info was not refreshing
|
|
# thus never getting the correct hostname.
|
|
@shell = nil
|
|
return false
|
|
end
|
|
|
|
def reset!
|
|
shell(true)
|
|
end
|
|
|
|
def shell(reload=false)
|
|
@shell = nil if reload
|
|
@shell ||= create_shell
|
|
end
|
|
|
|
def execute(command, opts={}, &block)
|
|
# If this is a *nix command with no Windows equivalent, don't run it
|
|
command = @cmd_filter.filter(command)
|
|
return 0 if command.empty?
|
|
|
|
opts = {
|
|
command: command,
|
|
elevated: false,
|
|
error_check: true,
|
|
error_class: Errors::WinRMBadExitStatus,
|
|
error_key: nil, # use the error_class message key
|
|
good_exit: 0,
|
|
shell: :powershell,
|
|
interactive: false,
|
|
}.merge(opts || {})
|
|
|
|
opts[:shell] = :elevated if opts[:elevated]
|
|
opts[:good_exit] = Array(opts[:good_exit])
|
|
@logger.debug("#{opts[:shell]} executing:\n#{command}")
|
|
output = shell.send(opts[:shell], command, opts, &block)
|
|
execution_output(output, opts)
|
|
end
|
|
alias_method :sudo, :execute
|
|
|
|
def test(command, opts=nil)
|
|
# If this is a *nix command (which we know about) with no Windows
|
|
# equivalent, assume failure
|
|
command = @cmd_filter.filter(command)
|
|
return false if command.empty?
|
|
|
|
opts = {
|
|
command: command,
|
|
elevated: false,
|
|
error_check: false,
|
|
}.merge(opts || {})
|
|
|
|
# If we're passed a *nix command which PS can't parse we get exit code
|
|
# 0, but output in stderr. We need to check both exit code and stderr.
|
|
output = shell.send(:powershell, command)
|
|
return output.exitcode == 0 && output.stderr.length == 0
|
|
end
|
|
|
|
def upload(from, to)
|
|
@logger.info("Uploading: #{from} to #{to}")
|
|
shell.upload(from, to)
|
|
end
|
|
|
|
def download(from, to)
|
|
@logger.info("Downloading: #{from} to #{to}")
|
|
shell.download(from, to)
|
|
end
|
|
|
|
protected
|
|
|
|
# This creates a new WinRMShell based on the information we know
|
|
# about this machine.
|
|
def create_shell
|
|
winrm_info = Helper.winrm_info(@machine)
|
|
|
|
WinRMShell.new(
|
|
winrm_info[:host],
|
|
winrm_info[:port],
|
|
@machine.config.winrm
|
|
)
|
|
end
|
|
|
|
# Handles the raw WinRM shell result and converts it to a
|
|
# standard Vagrant communicator result
|
|
def execution_output(output, opts)
|
|
if opts[:shell] == :wql
|
|
return output
|
|
elsif opts[:error_check] && \
|
|
!opts[:good_exit].include?(output.exitcode)
|
|
raise_execution_error(output, opts)
|
|
end
|
|
output.exitcode
|
|
end
|
|
|
|
def raise_execution_error(output, opts)
|
|
# WinRM can return multiple stderr and stdout entries
|
|
error_opts = opts.merge(
|
|
stdout: output.stdout,
|
|
stderr: output.stderr
|
|
)
|
|
|
|
# Use a different error message key if the caller gave us one,
|
|
# otherwise use the error's default message
|
|
error_opts[:_key] = opts[:error_key] if opts[:error_key]
|
|
|
|
# Raise the error, use the type the caller gave us or the comm default
|
|
raise opts[:error_class], error_opts
|
|
end
|
|
end #WinRM class
|
|
end
|
|
end
|